Implement EGBB probing and -first/secondDrawDepth
[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 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 "gettext.h"
152
153 #ifdef ENABLE_NLS
154 # define _(s) gettext (s)
155 # define N_(s) gettext_noop (s)
156 # define T_(s) gettext(s)
157 #else
158 # ifdef WIN32
159 #   define _(s) T_(s)
160 #   define N_(s) s
161 # else
162 #   define _(s) (s)
163 #   define N_(s) s
164 #   define T_(s) s
165 # endif
166 #endif
167
168
169 int establish P((void));
170 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
171                          char *buf, int count, int error));
172 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
173                       char *buf, int count, int error));
174 void SendToICS P((char *s));
175 void SendToICSDelayed P((char *s, long msdelay));
176 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
177 void HandleMachineMove P((char *message, ChessProgramState *cps));
178 int AutoPlayOneMove P((void));
179 int LoadGameOneMove P((ChessMove readAhead));
180 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
181 int LoadPositionFromFile P((char *filename, int n, char *title));
182 int SavePositionToFile P((char *filename));
183 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
184 void ShowMove P((int fromX, int fromY, int toX, int toY));
185 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
186                    /*char*/int promoChar));
187 void BackwardInner P((int target));
188 void ForwardInner P((int target));
189 int Adjudicate P((ChessProgramState *cps));
190 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
191 void EditPositionDone P((Boolean fakeRights));
192 void PrintOpponents P((FILE *fp));
193 void PrintPosition P((FILE *fp, int move));
194 void StartChessProgram P((ChessProgramState *cps));
195 void SendToProgram P((char *message, ChessProgramState *cps));
196 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
197 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
198                            char *buf, int count, int error));
199 void SendTimeControl P((ChessProgramState *cps,
200                         int mps, long tc, int inc, int sd, int st));
201 char *TimeControlTagValue P((void));
202 void Attention P((ChessProgramState *cps));
203 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
204 int ResurrectChessProgram P((void));
205 void DisplayComment P((int moveNumber, char *text));
206 void DisplayMove P((int moveNumber));
207
208 void ParseGameHistory P((char *game));
209 void ParseBoard12 P((char *string));
210 void KeepAlive P((void));
211 void StartClocks P((void));
212 void SwitchClocks P((int nr));
213 void StopClocks P((void));
214 void ResetClocks P((void));
215 char *PGNDate P((void));
216 void SetGameInfo P((void));
217 int RegisterMove P((void));
218 void MakeRegisteredMove P((void));
219 void TruncateGame P((void));
220 int looking_at P((char *, int *, char *));
221 void CopyPlayerNameIntoFileName P((char **, char *));
222 char *SavePart P((char *));
223 int SaveGameOldStyle P((FILE *));
224 int SaveGamePGN P((FILE *));
225 int CheckFlags P((void));
226 long NextTickLength P((long));
227 void CheckTimeControl P((void));
228 void show_bytes P((FILE *, char *, int));
229 int string_to_rating P((char *str));
230 void ParseFeatures P((char* args, ChessProgramState *cps));
231 void InitBackEnd3 P((void));
232 void FeatureDone P((ChessProgramState* cps, int val));
233 void InitChessProgram P((ChessProgramState *cps, int setup));
234 void OutputKibitz(int window, char *text);
235 int PerpetualChase(int first, int last);
236 int EngineOutputIsUp();
237 void InitDrawingSizes(int x, int y);
238 void NextMatchGame P((void));
239 int NextTourneyGame P((int nr, int *swap));
240 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
241 FILE *WriteTourneyFile P((char *results, FILE *f));
242 void DisplayTwoMachinesTitle P(());
243 static void ExcludeClick P((int index));
244 void ToggleSecond P((void));
245 void PauseEngine P((ChessProgramState *cps));
246 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
247
248 #ifdef WIN32
249        extern void ConsoleCreate();
250 #endif
251
252 ChessProgramState *WhitePlayer();
253 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
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 ChessSquare pieceSweep = EmptySquare;
293 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
294 int promoDefaultAltered;
295 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
296 static int initPing = -1;
297
298 /* States for ics_getting_history */
299 #define H_FALSE 0
300 #define H_REQUESTED 1
301 #define H_GOT_REQ_HEADER 2
302 #define H_GOT_UNREQ_HEADER 3
303 #define H_GETTING_MOVES 4
304 #define H_GOT_UNWANTED_HEADER 5
305
306 /* whosays values for GameEnds */
307 #define GE_ICS 0
308 #define GE_ENGINE 1
309 #define GE_PLAYER 2
310 #define GE_FILE 3
311 #define GE_XBOARD 4
312 #define GE_ENGINE1 5
313 #define GE_ENGINE2 6
314
315 /* Maximum number of games in a cmail message */
316 #define CMAIL_MAX_GAMES 20
317
318 /* Different types of move when calling RegisterMove */
319 #define CMAIL_MOVE   0
320 #define CMAIL_RESIGN 1
321 #define CMAIL_DRAW   2
322 #define CMAIL_ACCEPT 3
323
324 /* Different types of result to remember for each game */
325 #define CMAIL_NOT_RESULT 0
326 #define CMAIL_OLD_RESULT 1
327 #define CMAIL_NEW_RESULT 2
328
329 /* Telnet protocol constants */
330 #define TN_WILL 0373
331 #define TN_WONT 0374
332 #define TN_DO   0375
333 #define TN_DONT 0376
334 #define TN_IAC  0377
335 #define TN_ECHO 0001
336 #define TN_SGA  0003
337 #define TN_PORT 23
338
339 char*
340 safeStrCpy (char *dst, const char *src, size_t count)
341 { // [HGM] made safe
342   int i;
343   assert( dst != NULL );
344   assert( src != NULL );
345   assert( count > 0 );
346
347   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
348   if(  i == count && dst[count-1] != NULLCHAR)
349     {
350       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
351       if(appData.debugMode)
352         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
353     }
354
355   return dst;
356 }
357
358 /* Some compiler can't cast u64 to double
359  * This function do the job for us:
360
361  * We use the highest bit for cast, this only
362  * works if the highest bit is not
363  * in use (This should not happen)
364  *
365  * We used this for all compiler
366  */
367 double
368 u64ToDouble (u64 value)
369 {
370   double r;
371   u64 tmp = value & u64Const(0x7fffffffffffffff);
372   r = (double)(s64)tmp;
373   if (value & u64Const(0x8000000000000000))
374        r +=  9.2233720368547758080e18; /* 2^63 */
375  return r;
376 }
377
378 /* Fake up flags for now, as we aren't keeping track of castling
379    availability yet. [HGM] Change of logic: the flag now only
380    indicates the type of castlings allowed by the rule of the game.
381    The actual rights themselves are maintained in the array
382    castlingRights, as part of the game history, and are not probed
383    by this function.
384  */
385 int
386 PosFlags (index)
387 {
388   int flags = F_ALL_CASTLE_OK;
389   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
390   switch (gameInfo.variant) {
391   case VariantSuicide:
392     flags &= ~F_ALL_CASTLE_OK;
393   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
394     flags |= F_IGNORE_CHECK;
395   case VariantLosers:
396     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
397     break;
398   case VariantAtomic:
399     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
400     break;
401   case VariantKriegspiel:
402     flags |= F_KRIEGSPIEL_CAPTURE;
403     break;
404   case VariantCapaRandom:
405   case VariantFischeRandom:
406     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
407   case VariantNoCastle:
408   case VariantShatranj:
409   case VariantCourier:
410   case VariantMakruk:
411   case VariantASEAN:
412   case VariantGrand:
413     flags &= ~F_ALL_CASTLE_OK;
414     break;
415   default:
416     break;
417   }
418   return flags;
419 }
420
421 FILE *gameFileFP, *debugFP, *serverFP;
422 char *currentDebugFile; // [HGM] debug split: to remember name
423
424 /*
425     [AS] Note: sometimes, the sscanf() function is used to parse the input
426     into a fixed-size buffer. Because of this, we must be prepared to
427     receive strings as long as the size of the input buffer, which is currently
428     set to 4K for Windows and 8K for the rest.
429     So, we must either allocate sufficiently large buffers here, or
430     reduce the size of the input buffer in the input reading part.
431 */
432
433 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
434 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
435 char thinkOutput1[MSG_SIZ*10];
436
437 ChessProgramState first, second, pairing;
438
439 /* premove variables */
440 int premoveToX = 0;
441 int premoveToY = 0;
442 int premoveFromX = 0;
443 int premoveFromY = 0;
444 int premovePromoChar = 0;
445 int gotPremove = 0;
446 Boolean alarmSounded;
447 /* end premove variables */
448
449 char *ics_prefix = "$";
450 enum ICS_TYPE ics_type = ICS_GENERIC;
451
452 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
453 int pauseExamForwardMostMove = 0;
454 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
455 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
456 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
457 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
458 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
459 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
460 int whiteFlag = FALSE, blackFlag = FALSE;
461 int userOfferedDraw = FALSE;
462 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
463 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
464 int cmailMoveType[CMAIL_MAX_GAMES];
465 long ics_clock_paused = 0;
466 ProcRef icsPR = NoProc, cmailPR = NoProc;
467 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
468 GameMode gameMode = BeginningOfGame;
469 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
470 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
471 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
472 int hiddenThinkOutputState = 0; /* [AS] */
473 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
474 int adjudicateLossPlies = 6;
475 char white_holding[64], black_holding[64];
476 TimeMark lastNodeCountTime;
477 long lastNodeCount=0;
478 int shiftKey, controlKey; // [HGM] set by mouse handler
479
480 int have_sent_ICS_logon = 0;
481 int movesPerSession;
482 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
483 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
484 Boolean adjustedClock;
485 long timeControl_2; /* [AS] Allow separate time controls */
486 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
487 long timeRemaining[2][MAX_MOVES];
488 int matchGame = 0, nextGame = 0, roundNr = 0;
489 Boolean waitingForGame = FALSE, startingEngine = FALSE;
490 TimeMark programStartTime, pauseStart;
491 char ics_handle[MSG_SIZ];
492 int have_set_title = 0;
493
494 /* animateTraining preserves the state of appData.animate
495  * when Training mode is activated. This allows the
496  * response to be animated when appData.animate == TRUE and
497  * appData.animateDragging == TRUE.
498  */
499 Boolean animateTraining;
500
501 GameInfo gameInfo;
502
503 AppData appData;
504
505 Board boards[MAX_MOVES];
506 /* [HGM] Following 7 needed for accurate legality tests: */
507 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
508 signed char  initialRights[BOARD_FILES];
509 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
510 int   initialRulePlies, FENrulePlies;
511 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
512 int loadFlag = 0;
513 Boolean shuffleOpenings;
514 int mute; // mute all sounds
515
516 // [HGM] vari: next 12 to save and restore variations
517 #define MAX_VARIATIONS 10
518 int framePtr = MAX_MOVES-1; // points to free stack entry
519 int storedGames = 0;
520 int savedFirst[MAX_VARIATIONS];
521 int savedLast[MAX_VARIATIONS];
522 int savedFramePtr[MAX_VARIATIONS];
523 char *savedDetails[MAX_VARIATIONS];
524 ChessMove savedResult[MAX_VARIATIONS];
525
526 void PushTail P((int firstMove, int lastMove));
527 Boolean PopTail P((Boolean annotate));
528 void PushInner P((int firstMove, int lastMove));
529 void PopInner P((Boolean annotate));
530 void CleanupTail P((void));
531
532 ChessSquare  FIDEArray[2][BOARD_FILES] = {
533     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
534         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
536         BlackKing, BlackBishop, BlackKnight, BlackRook }
537 };
538
539 ChessSquare twoKingsArray[2][BOARD_FILES] = {
540     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
543         BlackKing, BlackKing, BlackKnight, BlackRook }
544 };
545
546 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
547     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
548         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
549     { BlackRook, BlackMan, BlackBishop, BlackQueen,
550         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
551 };
552
553 ChessSquare SpartanArray[2][BOARD_FILES] = {
554     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
555         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
556     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
557         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
558 };
559
560 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
561     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
562         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
563     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
564         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
565 };
566
567 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
568     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
569         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
570     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
571         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
572 };
573
574 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
575     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
576         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
577     { BlackRook, BlackKnight, BlackMan, BlackFerz,
578         BlackKing, BlackMan, BlackKnight, BlackRook }
579 };
580
581 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
582     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
583         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
584     { BlackRook, BlackKnight, BlackMan, BlackFerz,
585         BlackKing, BlackMan, BlackKnight, BlackRook }
586 };
587
588 ChessSquare  lionArray[2][BOARD_FILES] = {
589     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
590         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
591     { BlackRook, BlackLion, BlackBishop, BlackQueen,
592         BlackKing, BlackBishop, BlackKnight, BlackRook }
593 };
594
595
596 #if (BOARD_FILES>=10)
597 ChessSquare ShogiArray[2][BOARD_FILES] = {
598     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
599         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
600     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
601         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
602 };
603
604 ChessSquare XiangqiArray[2][BOARD_FILES] = {
605     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
606         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
607     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
608         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
609 };
610
611 ChessSquare CapablancaArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
613         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
615         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
616 };
617
618 ChessSquare GreatArray[2][BOARD_FILES] = {
619     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
620         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
621     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
622         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
623 };
624
625 ChessSquare JanusArray[2][BOARD_FILES] = {
626     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
627         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
628     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
629         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
630 };
631
632 ChessSquare GrandArray[2][BOARD_FILES] = {
633     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
634         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
635     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
636         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
637 };
638
639 ChessSquare ChuChessArray[2][BOARD_FILES] = {
640     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
641         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
642     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
643         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
644 };
645
646 #ifdef GOTHIC
647 ChessSquare GothicArray[2][BOARD_FILES] = {
648     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
649         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
650     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
651         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
652 };
653 #else // !GOTHIC
654 #define GothicArray CapablancaArray
655 #endif // !GOTHIC
656
657 #ifdef FALCON
658 ChessSquare FalconArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
660         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
662         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
663 };
664 #else // !FALCON
665 #define FalconArray CapablancaArray
666 #endif // !FALCON
667
668 #else // !(BOARD_FILES>=10)
669 #define XiangqiPosition FIDEArray
670 #define CapablancaArray FIDEArray
671 #define GothicArray FIDEArray
672 #define GreatArray FIDEArray
673 #endif // !(BOARD_FILES>=10)
674
675 #if (BOARD_FILES>=12)
676 ChessSquare CourierArray[2][BOARD_FILES] = {
677     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
678         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
679     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
680         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
681 };
682 ChessSquare ChuArray[6][BOARD_FILES] = {
683     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
684       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
685     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
686       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
687     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
688       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
689     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
690       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
691     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
692       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
693     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
694       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
695 };
696 #else // !(BOARD_FILES>=12)
697 #define CourierArray CapablancaArray
698 #define ChuArray CapablancaArray
699 #endif // !(BOARD_FILES>=12)
700
701
702 Board initialPosition;
703
704
705 /* Convert str to a rating. Checks for special cases of "----",
706
707    "++++", etc. Also strips ()'s */
708 int
709 string_to_rating (char *str)
710 {
711   while(*str && !isdigit(*str)) ++str;
712   if (!*str)
713     return 0;   /* One of the special "no rating" cases */
714   else
715     return atoi(str);
716 }
717
718 void
719 ClearProgramStats ()
720 {
721     /* Init programStats */
722     programStats.movelist[0] = 0;
723     programStats.depth = 0;
724     programStats.nr_moves = 0;
725     programStats.moves_left = 0;
726     programStats.nodes = 0;
727     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
728     programStats.score = 0;
729     programStats.got_only_move = 0;
730     programStats.got_fail = 0;
731     programStats.line_is_book = 0;
732 }
733
734 void
735 CommonEngineInit ()
736 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
737     if (appData.firstPlaysBlack) {
738         first.twoMachinesColor = "black\n";
739         second.twoMachinesColor = "white\n";
740     } else {
741         first.twoMachinesColor = "white\n";
742         second.twoMachinesColor = "black\n";
743     }
744
745     first.other = &second;
746     second.other = &first;
747
748     { float norm = 1;
749         if(appData.timeOddsMode) {
750             norm = appData.timeOdds[0];
751             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
752         }
753         first.timeOdds  = appData.timeOdds[0]/norm;
754         second.timeOdds = appData.timeOdds[1]/norm;
755     }
756
757     if(programVersion) free(programVersion);
758     if (appData.noChessProgram) {
759         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
760         sprintf(programVersion, "%s", PACKAGE_STRING);
761     } else {
762       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
763       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
764       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
765     }
766 }
767
768 void
769 UnloadEngine (ChessProgramState *cps)
770 {
771         /* Kill off first chess program */
772         if (cps->isr != NULL)
773           RemoveInputSource(cps->isr);
774         cps->isr = NULL;
775
776         if (cps->pr != NoProc) {
777             ExitAnalyzeMode();
778             DoSleep( appData.delayBeforeQuit );
779             SendToProgram("quit\n", cps);
780             DoSleep( appData.delayAfterQuit );
781             DestroyChildProcess(cps->pr, cps->useSigterm);
782         }
783         cps->pr = NoProc;
784         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
785 }
786
787 void
788 ClearOptions (ChessProgramState *cps)
789 {
790     int i;
791     cps->nrOptions = cps->comboCnt = 0;
792     for(i=0; i<MAX_OPTIONS; i++) {
793         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
794         cps->option[i].textValue = 0;
795     }
796 }
797
798 char *engineNames[] = {
799   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
800      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
801 N_("first"),
802   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
803      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
804 N_("second")
805 };
806
807 void
808 InitEngine (ChessProgramState *cps, int n)
809 {   // [HGM] all engine initialiation put in a function that does one engine
810
811     ClearOptions(cps);
812
813     cps->which = engineNames[n];
814     cps->maybeThinking = FALSE;
815     cps->pr = NoProc;
816     cps->isr = NULL;
817     cps->sendTime = 2;
818     cps->sendDrawOffers = 1;
819
820     cps->program = appData.chessProgram[n];
821     cps->host = appData.host[n];
822     cps->dir = appData.directory[n];
823     cps->initString = appData.engInitString[n];
824     cps->computerString = appData.computerString[n];
825     cps->useSigint  = TRUE;
826     cps->useSigterm = TRUE;
827     cps->reuse = appData.reuse[n];
828     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
829     cps->useSetboard = FALSE;
830     cps->useSAN = FALSE;
831     cps->usePing = FALSE;
832     cps->lastPing = 0;
833     cps->lastPong = 0;
834     cps->usePlayother = FALSE;
835     cps->useColors = TRUE;
836     cps->useUsermove = FALSE;
837     cps->sendICS = FALSE;
838     cps->sendName = appData.icsActive;
839     cps->sdKludge = FALSE;
840     cps->stKludge = FALSE;
841     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
842     TidyProgramName(cps->program, cps->host, cps->tidy);
843     cps->matchWins = 0;
844     ASSIGN(cps->variants, appData.variant);
845     cps->analysisSupport = 2; /* detect */
846     cps->analyzing = FALSE;
847     cps->initDone = FALSE;
848     cps->reload = FALSE;
849
850     /* New features added by Tord: */
851     cps->useFEN960 = FALSE;
852     cps->useOOCastle = TRUE;
853     /* End of new features added by Tord. */
854     cps->fenOverride  = appData.fenOverride[n];
855
856     /* [HGM] time odds: set factor for each machine */
857     cps->timeOdds  = appData.timeOdds[n];
858
859     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
860     cps->accumulateTC = appData.accumulateTC[n];
861     cps->maxNrOfSessions = 1;
862
863     /* [HGM] debug */
864     cps->debug = FALSE;
865
866     cps->drawDepth = appData.drawDepth[n];
867     cps->supportsNPS = UNKNOWN;
868     cps->memSize = FALSE;
869     cps->maxCores = FALSE;
870     ASSIGN(cps->egtFormats, "");
871
872     /* [HGM] options */
873     cps->optionSettings  = appData.engOptions[n];
874
875     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
876     cps->isUCI = appData.isUCI[n]; /* [AS] */
877     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
878     cps->highlight = 0;
879
880     if (appData.protocolVersion[n] > PROTOVER
881         || appData.protocolVersion[n] < 1)
882       {
883         char buf[MSG_SIZ];
884         int len;
885
886         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
887                        appData.protocolVersion[n]);
888         if( (len >= MSG_SIZ) && appData.debugMode )
889           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
890
891         DisplayFatalError(buf, 0, 2);
892       }
893     else
894       {
895         cps->protocolVersion = appData.protocolVersion[n];
896       }
897
898     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
899     ParseFeatures(appData.featureDefaults, cps);
900 }
901
902 ChessProgramState *savCps;
903
904 GameMode oldMode;
905
906 void
907 LoadEngine ()
908 {
909     int i;
910     if(WaitForEngine(savCps, LoadEngine)) return;
911     CommonEngineInit(); // recalculate time odds
912     if(gameInfo.variant != StringToVariant(appData.variant)) {
913         // we changed variant when loading the engine; this forces us to reset
914         Reset(TRUE, savCps != &first);
915         oldMode = BeginningOfGame; // to prevent restoring old mode
916     }
917     InitChessProgram(savCps, FALSE);
918     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
919     DisplayMessage("", "");
920     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
921     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
922     ThawUI();
923     SetGNUMode();
924     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
925 }
926
927 void
928 ReplaceEngine (ChessProgramState *cps, int n)
929 {
930     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
931     keepInfo = 1;
932     if(oldMode != BeginningOfGame) EditGameEvent();
933     keepInfo = 0;
934     UnloadEngine(cps);
935     appData.noChessProgram = FALSE;
936     appData.clockMode = TRUE;
937     InitEngine(cps, n);
938     UpdateLogos(TRUE);
939     if(n) return; // only startup first engine immediately; second can wait
940     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
941     LoadEngine();
942 }
943
944 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
945 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
946
947 static char resetOptions[] =
948         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
949         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
950         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
951         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
952
953 void
954 FloatToFront(char **list, char *engineLine)
955 {
956     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
957     int i=0;
958     if(appData.recentEngines <= 0) return;
959     TidyProgramName(engineLine, "localhost", tidy+1);
960     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
961     strncpy(buf+1, *list, MSG_SIZ-50);
962     if(p = strstr(buf, tidy)) { // tidy name appears in list
963         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
964         while(*p++ = *++q); // squeeze out
965     }
966     strcat(tidy, buf+1); // put list behind tidy name
967     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
968     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
969     ASSIGN(*list, tidy+1);
970 }
971
972 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
973
974 void
975 Load (ChessProgramState *cps, int i)
976 {
977     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
978     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
979         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
980         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
981         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
982         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
983         appData.firstProtocolVersion = PROTOVER;
984         ParseArgsFromString(buf);
985         SwapEngines(i);
986         ReplaceEngine(cps, i);
987         FloatToFront(&appData.recentEngineList, engineLine);
988         return;
989     }
990     p = engineName;
991     while(q = strchr(p, SLASH)) p = q+1;
992     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
993     if(engineDir[0] != NULLCHAR) {
994         ASSIGN(appData.directory[i], engineDir); p = engineName;
995     } else if(p != engineName) { // derive directory from engine path, when not given
996         p[-1] = 0;
997         ASSIGN(appData.directory[i], engineName);
998         p[-1] = SLASH;
999         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1000     } else { ASSIGN(appData.directory[i], "."); }
1001     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1002     if(params[0]) {
1003         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1004         snprintf(command, MSG_SIZ, "%s %s", p, params);
1005         p = command;
1006     }
1007     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1008     ASSIGN(appData.chessProgram[i], p);
1009     appData.isUCI[i] = isUCI;
1010     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1011     appData.hasOwnBookUCI[i] = hasBook;
1012     if(!nickName[0]) useNick = FALSE;
1013     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1014     if(addToList) {
1015         int len;
1016         char quote;
1017         q = firstChessProgramNames;
1018         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1019         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1020         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1021                         quote, p, quote, appData.directory[i],
1022                         useNick ? " -fn \"" : "",
1023                         useNick ? nickName : "",
1024                         useNick ? "\"" : "",
1025                         v1 ? " -firstProtocolVersion 1" : "",
1026                         hasBook ? "" : " -fNoOwnBookUCI",
1027                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1028                         storeVariant ? " -variant " : "",
1029                         storeVariant ? VariantName(gameInfo.variant) : "");
1030         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1031         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1032         if(insert != q) insert[-1] = NULLCHAR;
1033         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1034         if(q)   free(q);
1035         FloatToFront(&appData.recentEngineList, buf);
1036     }
1037     ReplaceEngine(cps, i);
1038 }
1039
1040 void
1041 InitTimeControls ()
1042 {
1043     int matched, min, sec;
1044     /*
1045      * Parse timeControl resource
1046      */
1047     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1048                           appData.movesPerSession)) {
1049         char buf[MSG_SIZ];
1050         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1051         DisplayFatalError(buf, 0, 2);
1052     }
1053
1054     /*
1055      * Parse searchTime resource
1056      */
1057     if (*appData.searchTime != NULLCHAR) {
1058         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1059         if (matched == 1) {
1060             searchTime = min * 60;
1061         } else if (matched == 2) {
1062             searchTime = min * 60 + sec;
1063         } else {
1064             char buf[MSG_SIZ];
1065             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1066             DisplayFatalError(buf, 0, 2);
1067         }
1068     }
1069 }
1070
1071 void
1072 InitBackEnd1 ()
1073 {
1074
1075     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1076     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1077
1078     GetTimeMark(&programStartTime);
1079     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1080     appData.seedBase = random() + (random()<<15);
1081     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1082
1083     ClearProgramStats();
1084     programStats.ok_to_send = 1;
1085     programStats.seen_stat = 0;
1086
1087     /*
1088      * Initialize game list
1089      */
1090     ListNew(&gameList);
1091
1092
1093     /*
1094      * Internet chess server status
1095      */
1096     if (appData.icsActive) {
1097         appData.matchMode = FALSE;
1098         appData.matchGames = 0;
1099 #if ZIPPY
1100         appData.noChessProgram = !appData.zippyPlay;
1101 #else
1102         appData.zippyPlay = FALSE;
1103         appData.zippyTalk = FALSE;
1104         appData.noChessProgram = TRUE;
1105 #endif
1106         if (*appData.icsHelper != NULLCHAR) {
1107             appData.useTelnet = TRUE;
1108             appData.telnetProgram = appData.icsHelper;
1109         }
1110     } else {
1111         appData.zippyTalk = appData.zippyPlay = FALSE;
1112     }
1113
1114     /* [AS] Initialize pv info list [HGM] and game state */
1115     {
1116         int i, j;
1117
1118         for( i=0; i<=framePtr; i++ ) {
1119             pvInfoList[i].depth = -1;
1120             boards[i][EP_STATUS] = EP_NONE;
1121             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1122         }
1123     }
1124
1125     InitTimeControls();
1126
1127     /* [AS] Adjudication threshold */
1128     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1129
1130     InitEngine(&first, 0);
1131     InitEngine(&second, 1);
1132     CommonEngineInit();
1133
1134     pairing.which = "pairing"; // pairing engine
1135     pairing.pr = NoProc;
1136     pairing.isr = NULL;
1137     pairing.program = appData.pairingEngine;
1138     pairing.host = "localhost";
1139     pairing.dir = ".";
1140
1141     if (appData.icsActive) {
1142         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1143     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1144         appData.clockMode = FALSE;
1145         first.sendTime = second.sendTime = 0;
1146     }
1147
1148 #if ZIPPY
1149     /* Override some settings from environment variables, for backward
1150        compatibility.  Unfortunately it's not feasible to have the env
1151        vars just set defaults, at least in xboard.  Ugh.
1152     */
1153     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1154       ZippyInit();
1155     }
1156 #endif
1157
1158     if (!appData.icsActive) {
1159       char buf[MSG_SIZ];
1160       int len;
1161
1162       /* Check for variants that are supported only in ICS mode,
1163          or not at all.  Some that are accepted here nevertheless
1164          have bugs; see comments below.
1165       */
1166       VariantClass variant = StringToVariant(appData.variant);
1167       switch (variant) {
1168       case VariantBughouse:     /* need four players and two boards */
1169       case VariantKriegspiel:   /* need to hide pieces and move details */
1170         /* case VariantFischeRandom: (Fabien: moved below) */
1171         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1172         if( (len >= MSG_SIZ) && appData.debugMode )
1173           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1174
1175         DisplayFatalError(buf, 0, 2);
1176         return;
1177
1178       case VariantUnknown:
1179       case VariantLoadable:
1180       case Variant29:
1181       case Variant30:
1182       case Variant31:
1183       case Variant32:
1184       case Variant33:
1185       case Variant34:
1186       case Variant35:
1187       case Variant36:
1188       default:
1189         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1190         if( (len >= MSG_SIZ) && appData.debugMode )
1191           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1192
1193         DisplayFatalError(buf, 0, 2);
1194         return;
1195
1196       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1197       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1198       case VariantGothic:     /* [HGM] should work */
1199       case VariantCapablanca: /* [HGM] should work */
1200       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1201       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1202       case VariantChu:        /* [HGM] experimental */
1203       case VariantKnightmate: /* [HGM] should work */
1204       case VariantCylinder:   /* [HGM] untested */
1205       case VariantFalcon:     /* [HGM] untested */
1206       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1207                                  offboard interposition not understood */
1208       case VariantNormal:     /* definitely works! */
1209       case VariantWildCastle: /* pieces not automatically shuffled */
1210       case VariantNoCastle:   /* pieces not automatically shuffled */
1211       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1212       case VariantLosers:     /* should work except for win condition,
1213                                  and doesn't know captures are mandatory */
1214       case VariantSuicide:    /* should work except for win condition,
1215                                  and doesn't know captures are mandatory */
1216       case VariantGiveaway:   /* should work except for win condition,
1217                                  and doesn't know captures are mandatory */
1218       case VariantTwoKings:   /* should work */
1219       case VariantAtomic:     /* should work except for win condition */
1220       case Variant3Check:     /* should work except for win condition */
1221       case VariantShatranj:   /* should work except for all win conditions */
1222       case VariantMakruk:     /* should work except for draw countdown */
1223       case VariantASEAN :     /* should work except for draw countdown */
1224       case VariantBerolina:   /* might work if TestLegality is off */
1225       case VariantCapaRandom: /* should work */
1226       case VariantJanus:      /* should work */
1227       case VariantSuper:      /* experimental */
1228       case VariantGreat:      /* experimental, requires legality testing to be off */
1229       case VariantSChess:     /* S-Chess, should work */
1230       case VariantGrand:      /* should work */
1231       case VariantSpartan:    /* should work */
1232       case VariantLion:       /* should work */
1233       case VariantChuChess:   /* should work */
1234         break;
1235       }
1236     }
1237
1238 }
1239
1240 int
1241 NextIntegerFromString (char ** str, long * value)
1242 {
1243     int result = -1;
1244     char * s = *str;
1245
1246     while( *s == ' ' || *s == '\t' ) {
1247         s++;
1248     }
1249
1250     *value = 0;
1251
1252     if( *s >= '0' && *s <= '9' ) {
1253         while( *s >= '0' && *s <= '9' ) {
1254             *value = *value * 10 + (*s - '0');
1255             s++;
1256         }
1257
1258         result = 0;
1259     }
1260
1261     *str = s;
1262
1263     return result;
1264 }
1265
1266 int
1267 NextTimeControlFromString (char ** str, long * value)
1268 {
1269     long temp;
1270     int result = NextIntegerFromString( str, &temp );
1271
1272     if( result == 0 ) {
1273         *value = temp * 60; /* Minutes */
1274         if( **str == ':' ) {
1275             (*str)++;
1276             result = NextIntegerFromString( str, &temp );
1277             *value += temp; /* Seconds */
1278         }
1279     }
1280
1281     return result;
1282 }
1283
1284 int
1285 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1286 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1287     int result = -1, type = 0; long temp, temp2;
1288
1289     if(**str != ':') return -1; // old params remain in force!
1290     (*str)++;
1291     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1292     if( NextIntegerFromString( str, &temp ) ) return -1;
1293     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1294
1295     if(**str != '/') {
1296         /* time only: incremental or sudden-death time control */
1297         if(**str == '+') { /* increment follows; read it */
1298             (*str)++;
1299             if(**str == '!') type = *(*str)++; // Bronstein TC
1300             if(result = NextIntegerFromString( str, &temp2)) return -1;
1301             *inc = temp2 * 1000;
1302             if(**str == '.') { // read fraction of increment
1303                 char *start = ++(*str);
1304                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1305                 temp2 *= 1000;
1306                 while(start++ < *str) temp2 /= 10;
1307                 *inc += temp2;
1308             }
1309         } else *inc = 0;
1310         *moves = 0; *tc = temp * 1000; *incType = type;
1311         return 0;
1312     }
1313
1314     (*str)++; /* classical time control */
1315     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1316
1317     if(result == 0) {
1318         *moves = temp;
1319         *tc    = temp2 * 1000;
1320         *inc   = 0;
1321         *incType = type;
1322     }
1323     return result;
1324 }
1325
1326 int
1327 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1328 {   /* [HGM] get time to add from the multi-session time-control string */
1329     int incType, moves=1; /* kludge to force reading of first session */
1330     long time, increment;
1331     char *s = tcString;
1332
1333     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1334     do {
1335         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1336         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1337         if(movenr == -1) return time;    /* last move before new session     */
1338         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1339         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1340         if(!moves) return increment;     /* current session is incremental   */
1341         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1342     } while(movenr >= -1);               /* try again for next session       */
1343
1344     return 0; // no new time quota on this move
1345 }
1346
1347 int
1348 ParseTimeControl (char *tc, float ti, int mps)
1349 {
1350   long tc1;
1351   long tc2;
1352   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1353   int min, sec=0;
1354
1355   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1356   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1357       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1358   if(ti > 0) {
1359
1360     if(mps)
1361       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1362     else
1363       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1364   } else {
1365     if(mps)
1366       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1367     else
1368       snprintf(buf, MSG_SIZ, ":%s", mytc);
1369   }
1370   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1371
1372   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1373     return FALSE;
1374   }
1375
1376   if( *tc == '/' ) {
1377     /* Parse second time control */
1378     tc++;
1379
1380     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1381       return FALSE;
1382     }
1383
1384     if( tc2 == 0 ) {
1385       return FALSE;
1386     }
1387
1388     timeControl_2 = tc2 * 1000;
1389   }
1390   else {
1391     timeControl_2 = 0;
1392   }
1393
1394   if( tc1 == 0 ) {
1395     return FALSE;
1396   }
1397
1398   timeControl = tc1 * 1000;
1399
1400   if (ti >= 0) {
1401     timeIncrement = ti * 1000;  /* convert to ms */
1402     movesPerSession = 0;
1403   } else {
1404     timeIncrement = 0;
1405     movesPerSession = mps;
1406   }
1407   return TRUE;
1408 }
1409
1410 void
1411 InitBackEnd2 ()
1412 {
1413     if (appData.debugMode) {
1414 #    ifdef __GIT_VERSION
1415       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1416 #    else
1417       fprintf(debugFP, "Version: %s\n", programVersion);
1418 #    endif
1419     }
1420     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1421
1422     set_cont_sequence(appData.wrapContSeq);
1423     if (appData.matchGames > 0) {
1424         appData.matchMode = TRUE;
1425     } else if (appData.matchMode) {
1426         appData.matchGames = 1;
1427     }
1428     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1429         appData.matchGames = appData.sameColorGames;
1430     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1431         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1432         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1433     }
1434     Reset(TRUE, FALSE);
1435     if (appData.noChessProgram || first.protocolVersion == 1) {
1436       InitBackEnd3();
1437     } else {
1438       /* kludge: allow timeout for initial "feature" commands */
1439       FreezeUI();
1440       DisplayMessage("", _("Starting chess program"));
1441       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1442     }
1443 }
1444
1445 int
1446 CalculateIndex (int index, int gameNr)
1447 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1448     int res;
1449     if(index > 0) return index; // fixed nmber
1450     if(index == 0) return 1;
1451     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1452     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1453     return res;
1454 }
1455
1456 int
1457 LoadGameOrPosition (int gameNr)
1458 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1459     if (*appData.loadGameFile != NULLCHAR) {
1460         if (!LoadGameFromFile(appData.loadGameFile,
1461                 CalculateIndex(appData.loadGameIndex, gameNr),
1462                               appData.loadGameFile, FALSE)) {
1463             DisplayFatalError(_("Bad game file"), 0, 1);
1464             return 0;
1465         }
1466     } else if (*appData.loadPositionFile != NULLCHAR) {
1467         if (!LoadPositionFromFile(appData.loadPositionFile,
1468                 CalculateIndex(appData.loadPositionIndex, gameNr),
1469                                   appData.loadPositionFile)) {
1470             DisplayFatalError(_("Bad position file"), 0, 1);
1471             return 0;
1472         }
1473     }
1474     return 1;
1475 }
1476
1477 void
1478 ReserveGame (int gameNr, char resChar)
1479 {
1480     FILE *tf = fopen(appData.tourneyFile, "r+");
1481     char *p, *q, c, buf[MSG_SIZ];
1482     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1483     safeStrCpy(buf, lastMsg, MSG_SIZ);
1484     DisplayMessage(_("Pick new game"), "");
1485     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1486     ParseArgsFromFile(tf);
1487     p = q = appData.results;
1488     if(appData.debugMode) {
1489       char *r = appData.participants;
1490       fprintf(debugFP, "results = '%s'\n", p);
1491       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1492       fprintf(debugFP, "\n");
1493     }
1494     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1495     nextGame = q - p;
1496     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1497     safeStrCpy(q, p, strlen(p) + 2);
1498     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1499     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1500     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1501         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1502         q[nextGame] = '*';
1503     }
1504     fseek(tf, -(strlen(p)+4), SEEK_END);
1505     c = fgetc(tf);
1506     if(c != '"') // depending on DOS or Unix line endings we can be one off
1507          fseek(tf, -(strlen(p)+2), SEEK_END);
1508     else fseek(tf, -(strlen(p)+3), SEEK_END);
1509     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1510     DisplayMessage(buf, "");
1511     free(p); appData.results = q;
1512     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1513        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1514       int round = appData.defaultMatchGames * appData.tourneyType;
1515       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1516          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1517         UnloadEngine(&first);  // next game belongs to other pairing;
1518         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1519     }
1520     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1521 }
1522
1523 void
1524 MatchEvent (int mode)
1525 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1526         int dummy;
1527         if(matchMode) { // already in match mode: switch it off
1528             abortMatch = TRUE;
1529             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1530             return;
1531         }
1532 //      if(gameMode != BeginningOfGame) {
1533 //          DisplayError(_("You can only start a match from the initial position."), 0);
1534 //          return;
1535 //      }
1536         abortMatch = FALSE;
1537         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1538         /* Set up machine vs. machine match */
1539         nextGame = 0;
1540         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1541         if(appData.tourneyFile[0]) {
1542             ReserveGame(-1, 0);
1543             if(nextGame > appData.matchGames) {
1544                 char buf[MSG_SIZ];
1545                 if(strchr(appData.results, '*') == NULL) {
1546                     FILE *f;
1547                     appData.tourneyCycles++;
1548                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1549                         fclose(f);
1550                         NextTourneyGame(-1, &dummy);
1551                         ReserveGame(-1, 0);
1552                         if(nextGame <= appData.matchGames) {
1553                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1554                             matchMode = mode;
1555                             ScheduleDelayedEvent(NextMatchGame, 10000);
1556                             return;
1557                         }
1558                     }
1559                 }
1560                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1561                 DisplayError(buf, 0);
1562                 appData.tourneyFile[0] = 0;
1563                 return;
1564             }
1565         } else
1566         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1567             DisplayFatalError(_("Can't have a match with no chess programs"),
1568                               0, 2);
1569             return;
1570         }
1571         matchMode = mode;
1572         matchGame = roundNr = 1;
1573         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1574         NextMatchGame();
1575 }
1576
1577 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1578
1579 void
1580 InitBackEnd3 P((void))
1581 {
1582     GameMode initialMode;
1583     char buf[MSG_SIZ];
1584     int err, len;
1585
1586     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1587        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1588         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1589        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1590        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1591         char c, *q = first.variants, *p = strchr(q, ',');
1592         if(p) *p = NULLCHAR;
1593         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1594             int w, h, s;
1595             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1596                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1597             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1598             Reset(TRUE, FALSE);         // and re-initialize
1599         }
1600         if(p) *p = ',';
1601     }
1602
1603     InitChessProgram(&first, startedFromSetupPosition);
1604
1605     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1606         free(programVersion);
1607         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1608         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1609         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1610     }
1611
1612     if (appData.icsActive) {
1613 #ifdef WIN32
1614         /* [DM] Make a console window if needed [HGM] merged ifs */
1615         ConsoleCreate();
1616 #endif
1617         err = establish();
1618         if (err != 0)
1619           {
1620             if (*appData.icsCommPort != NULLCHAR)
1621               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1622                              appData.icsCommPort);
1623             else
1624               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1625                         appData.icsHost, appData.icsPort);
1626
1627             if( (len >= MSG_SIZ) && appData.debugMode )
1628               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1629
1630             DisplayFatalError(buf, err, 1);
1631             return;
1632         }
1633         SetICSMode();
1634         telnetISR =
1635           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1636         fromUserISR =
1637           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1638         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1639             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1640     } else if (appData.noChessProgram) {
1641         SetNCPMode();
1642     } else {
1643         SetGNUMode();
1644     }
1645
1646     if (*appData.cmailGameName != NULLCHAR) {
1647         SetCmailMode();
1648         OpenLoopback(&cmailPR);
1649         cmailISR =
1650           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1651     }
1652
1653     ThawUI();
1654     DisplayMessage("", "");
1655     if (StrCaseCmp(appData.initialMode, "") == 0) {
1656       initialMode = BeginningOfGame;
1657       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1658         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1659         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1660         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1661         ModeHighlight();
1662       }
1663     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1664       initialMode = TwoMachinesPlay;
1665     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1666       initialMode = AnalyzeFile;
1667     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1668       initialMode = AnalyzeMode;
1669     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1670       initialMode = MachinePlaysWhite;
1671     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1672       initialMode = MachinePlaysBlack;
1673     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1674       initialMode = EditGame;
1675     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1676       initialMode = EditPosition;
1677     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1678       initialMode = Training;
1679     } else {
1680       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1681       if( (len >= MSG_SIZ) && appData.debugMode )
1682         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1683
1684       DisplayFatalError(buf, 0, 2);
1685       return;
1686     }
1687
1688     if (appData.matchMode) {
1689         if(appData.tourneyFile[0]) { // start tourney from command line
1690             FILE *f;
1691             if(f = fopen(appData.tourneyFile, "r")) {
1692                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1693                 fclose(f);
1694                 appData.clockMode = TRUE;
1695                 SetGNUMode();
1696             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1697         }
1698         MatchEvent(TRUE);
1699     } else if (*appData.cmailGameName != NULLCHAR) {
1700         /* Set up cmail mode */
1701         ReloadCmailMsgEvent(TRUE);
1702     } else {
1703         /* Set up other modes */
1704         if (initialMode == AnalyzeFile) {
1705           if (*appData.loadGameFile == NULLCHAR) {
1706             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1707             return;
1708           }
1709         }
1710         if (*appData.loadGameFile != NULLCHAR) {
1711             (void) LoadGameFromFile(appData.loadGameFile,
1712                                     appData.loadGameIndex,
1713                                     appData.loadGameFile, TRUE);
1714         } else if (*appData.loadPositionFile != NULLCHAR) {
1715             (void) LoadPositionFromFile(appData.loadPositionFile,
1716                                         appData.loadPositionIndex,
1717                                         appData.loadPositionFile);
1718             /* [HGM] try to make self-starting even after FEN load */
1719             /* to allow automatic setup of fairy variants with wtm */
1720             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1721                 gameMode = BeginningOfGame;
1722                 setboardSpoiledMachineBlack = 1;
1723             }
1724             /* [HGM] loadPos: make that every new game uses the setup */
1725             /* from file as long as we do not switch variant          */
1726             if(!blackPlaysFirst) {
1727                 startedFromPositionFile = TRUE;
1728                 CopyBoard(filePosition, boards[0]);
1729             }
1730         }
1731         if (initialMode == AnalyzeMode) {
1732           if (appData.noChessProgram) {
1733             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1734             return;
1735           }
1736           if (appData.icsActive) {
1737             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1738             return;
1739           }
1740           AnalyzeModeEvent();
1741         } else if (initialMode == AnalyzeFile) {
1742           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1743           ShowThinkingEvent();
1744           AnalyzeFileEvent();
1745           AnalysisPeriodicEvent(1);
1746         } else if (initialMode == MachinePlaysWhite) {
1747           if (appData.noChessProgram) {
1748             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1749                               0, 2);
1750             return;
1751           }
1752           if (appData.icsActive) {
1753             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1754                               0, 2);
1755             return;
1756           }
1757           MachineWhiteEvent();
1758         } else if (initialMode == MachinePlaysBlack) {
1759           if (appData.noChessProgram) {
1760             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1761                               0, 2);
1762             return;
1763           }
1764           if (appData.icsActive) {
1765             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1766                               0, 2);
1767             return;
1768           }
1769           MachineBlackEvent();
1770         } else if (initialMode == TwoMachinesPlay) {
1771           if (appData.noChessProgram) {
1772             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1773                               0, 2);
1774             return;
1775           }
1776           if (appData.icsActive) {
1777             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1778                               0, 2);
1779             return;
1780           }
1781           TwoMachinesEvent();
1782         } else if (initialMode == EditGame) {
1783           EditGameEvent();
1784         } else if (initialMode == EditPosition) {
1785           EditPositionEvent();
1786         } else if (initialMode == Training) {
1787           if (*appData.loadGameFile == NULLCHAR) {
1788             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1789             return;
1790           }
1791           TrainingEvent();
1792         }
1793     }
1794 }
1795
1796 void
1797 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1798 {
1799     DisplayBook(current+1);
1800
1801     MoveHistorySet( movelist, first, last, current, pvInfoList );
1802
1803     EvalGraphSet( first, last, current, pvInfoList );
1804
1805     MakeEngineOutputTitle();
1806 }
1807
1808 /*
1809  * Establish will establish a contact to a remote host.port.
1810  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1811  *  used to talk to the host.
1812  * Returns 0 if okay, error code if not.
1813  */
1814 int
1815 establish ()
1816 {
1817     char buf[MSG_SIZ];
1818
1819     if (*appData.icsCommPort != NULLCHAR) {
1820         /* Talk to the host through a serial comm port */
1821         return OpenCommPort(appData.icsCommPort, &icsPR);
1822
1823     } else if (*appData.gateway != NULLCHAR) {
1824         if (*appData.remoteShell == NULLCHAR) {
1825             /* Use the rcmd protocol to run telnet program on a gateway host */
1826             snprintf(buf, sizeof(buf), "%s %s %s",
1827                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1828             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1829
1830         } else {
1831             /* Use the rsh program to run telnet program on a gateway host */
1832             if (*appData.remoteUser == NULLCHAR) {
1833                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1834                         appData.gateway, appData.telnetProgram,
1835                         appData.icsHost, appData.icsPort);
1836             } else {
1837                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1838                         appData.remoteShell, appData.gateway,
1839                         appData.remoteUser, appData.telnetProgram,
1840                         appData.icsHost, appData.icsPort);
1841             }
1842             return StartChildProcess(buf, "", &icsPR);
1843
1844         }
1845     } else if (appData.useTelnet) {
1846         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1847
1848     } else {
1849         /* TCP socket interface differs somewhat between
1850            Unix and NT; handle details in the front end.
1851            */
1852         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1853     }
1854 }
1855
1856 void
1857 EscapeExpand (char *p, char *q)
1858 {       // [HGM] initstring: routine to shape up string arguments
1859         while(*p++ = *q++) if(p[-1] == '\\')
1860             switch(*q++) {
1861                 case 'n': p[-1] = '\n'; break;
1862                 case 'r': p[-1] = '\r'; break;
1863                 case 't': p[-1] = '\t'; break;
1864                 case '\\': p[-1] = '\\'; break;
1865                 case 0: *p = 0; return;
1866                 default: p[-1] = q[-1]; break;
1867             }
1868 }
1869
1870 void
1871 show_bytes (FILE *fp, char *buf, int count)
1872 {
1873     while (count--) {
1874         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1875             fprintf(fp, "\\%03o", *buf & 0xff);
1876         } else {
1877             putc(*buf, fp);
1878         }
1879         buf++;
1880     }
1881     fflush(fp);
1882 }
1883
1884 /* Returns an errno value */
1885 int
1886 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1887 {
1888     char buf[8192], *p, *q, *buflim;
1889     int left, newcount, outcount;
1890
1891     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1892         *appData.gateway != NULLCHAR) {
1893         if (appData.debugMode) {
1894             fprintf(debugFP, ">ICS: ");
1895             show_bytes(debugFP, message, count);
1896             fprintf(debugFP, "\n");
1897         }
1898         return OutputToProcess(pr, message, count, outError);
1899     }
1900
1901     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1902     p = message;
1903     q = buf;
1904     left = count;
1905     newcount = 0;
1906     while (left) {
1907         if (q >= buflim) {
1908             if (appData.debugMode) {
1909                 fprintf(debugFP, ">ICS: ");
1910                 show_bytes(debugFP, buf, newcount);
1911                 fprintf(debugFP, "\n");
1912             }
1913             outcount = OutputToProcess(pr, buf, newcount, outError);
1914             if (outcount < newcount) return -1; /* to be sure */
1915             q = buf;
1916             newcount = 0;
1917         }
1918         if (*p == '\n') {
1919             *q++ = '\r';
1920             newcount++;
1921         } else if (((unsigned char) *p) == TN_IAC) {
1922             *q++ = (char) TN_IAC;
1923             newcount ++;
1924         }
1925         *q++ = *p++;
1926         newcount++;
1927         left--;
1928     }
1929     if (appData.debugMode) {
1930         fprintf(debugFP, ">ICS: ");
1931         show_bytes(debugFP, buf, newcount);
1932         fprintf(debugFP, "\n");
1933     }
1934     outcount = OutputToProcess(pr, buf, newcount, outError);
1935     if (outcount < newcount) return -1; /* to be sure */
1936     return count;
1937 }
1938
1939 void
1940 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1941 {
1942     int outError, outCount;
1943     static int gotEof = 0;
1944     static FILE *ini;
1945
1946     /* Pass data read from player on to ICS */
1947     if (count > 0) {
1948         gotEof = 0;
1949         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1950         if (outCount < count) {
1951             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1952         }
1953         if(have_sent_ICS_logon == 2) {
1954           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1955             fprintf(ini, "%s", message);
1956             have_sent_ICS_logon = 3;
1957           } else
1958             have_sent_ICS_logon = 1;
1959         } else if(have_sent_ICS_logon == 3) {
1960             fprintf(ini, "%s", message);
1961             fclose(ini);
1962           have_sent_ICS_logon = 1;
1963         }
1964     } else if (count < 0) {
1965         RemoveInputSource(isr);
1966         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1967     } else if (gotEof++ > 0) {
1968         RemoveInputSource(isr);
1969         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1970     }
1971 }
1972
1973 void
1974 KeepAlive ()
1975 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1976     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1977     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1978     SendToICS("date\n");
1979     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1980 }
1981
1982 /* added routine for printf style output to ics */
1983 void
1984 ics_printf (char *format, ...)
1985 {
1986     char buffer[MSG_SIZ];
1987     va_list args;
1988
1989     va_start(args, format);
1990     vsnprintf(buffer, sizeof(buffer), format, args);
1991     buffer[sizeof(buffer)-1] = '\0';
1992     SendToICS(buffer);
1993     va_end(args);
1994 }
1995
1996 void
1997 SendToICS (char *s)
1998 {
1999     int count, outCount, outError;
2000
2001     if (icsPR == NoProc) return;
2002
2003     count = strlen(s);
2004     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2005     if (outCount < count) {
2006         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2007     }
2008 }
2009
2010 /* This is used for sending logon scripts to the ICS. Sending
2011    without a delay causes problems when using timestamp on ICC
2012    (at least on my machine). */
2013 void
2014 SendToICSDelayed (char *s, long msdelay)
2015 {
2016     int count, outCount, outError;
2017
2018     if (icsPR == NoProc) return;
2019
2020     count = strlen(s);
2021     if (appData.debugMode) {
2022         fprintf(debugFP, ">ICS: ");
2023         show_bytes(debugFP, s, count);
2024         fprintf(debugFP, "\n");
2025     }
2026     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2027                                       msdelay);
2028     if (outCount < count) {
2029         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2030     }
2031 }
2032
2033
2034 /* Remove all highlighting escape sequences in s
2035    Also deletes any suffix starting with '('
2036    */
2037 char *
2038 StripHighlightAndTitle (char *s)
2039 {
2040     static char retbuf[MSG_SIZ];
2041     char *p = retbuf;
2042
2043     while (*s != NULLCHAR) {
2044         while (*s == '\033') {
2045             while (*s != NULLCHAR && !isalpha(*s)) s++;
2046             if (*s != NULLCHAR) s++;
2047         }
2048         while (*s != NULLCHAR && *s != '\033') {
2049             if (*s == '(' || *s == '[') {
2050                 *p = NULLCHAR;
2051                 return retbuf;
2052             }
2053             *p++ = *s++;
2054         }
2055     }
2056     *p = NULLCHAR;
2057     return retbuf;
2058 }
2059
2060 /* Remove all highlighting escape sequences in s */
2061 char *
2062 StripHighlight (char *s)
2063 {
2064     static char retbuf[MSG_SIZ];
2065     char *p = retbuf;
2066
2067     while (*s != NULLCHAR) {
2068         while (*s == '\033') {
2069             while (*s != NULLCHAR && !isalpha(*s)) s++;
2070             if (*s != NULLCHAR) s++;
2071         }
2072         while (*s != NULLCHAR && *s != '\033') {
2073             *p++ = *s++;
2074         }
2075     }
2076     *p = NULLCHAR;
2077     return retbuf;
2078 }
2079
2080 char engineVariant[MSG_SIZ];
2081 char *variantNames[] = VARIANT_NAMES;
2082 char *
2083 VariantName (VariantClass v)
2084 {
2085     if(v == VariantUnknown || *engineVariant) return engineVariant;
2086     return variantNames[v];
2087 }
2088
2089
2090 /* Identify a variant from the strings the chess servers use or the
2091    PGN Variant tag names we use. */
2092 VariantClass
2093 StringToVariant (char *e)
2094 {
2095     char *p;
2096     int wnum = -1;
2097     VariantClass v = VariantNormal;
2098     int i, found = FALSE;
2099     char buf[MSG_SIZ];
2100     int len;
2101
2102     if (!e) return v;
2103
2104     /* [HGM] skip over optional board-size prefixes */
2105     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2106         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2107         while( *e++ != '_');
2108     }
2109
2110     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2111         v = VariantNormal;
2112         found = TRUE;
2113     } else
2114     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2115       if (p = StrCaseStr(e, variantNames[i])) {
2116         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2117         v = (VariantClass) i;
2118         found = TRUE;
2119         break;
2120       }
2121     }
2122
2123     if (!found) {
2124       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2125           || StrCaseStr(e, "wild/fr")
2126           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2127         v = VariantFischeRandom;
2128       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2129                  (i = 1, p = StrCaseStr(e, "w"))) {
2130         p += i;
2131         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2132         if (isdigit(*p)) {
2133           wnum = atoi(p);
2134         } else {
2135           wnum = -1;
2136         }
2137         switch (wnum) {
2138         case 0: /* FICS only, actually */
2139         case 1:
2140           /* Castling legal even if K starts on d-file */
2141           v = VariantWildCastle;
2142           break;
2143         case 2:
2144         case 3:
2145         case 4:
2146           /* Castling illegal even if K & R happen to start in
2147              normal positions. */
2148           v = VariantNoCastle;
2149           break;
2150         case 5:
2151         case 7:
2152         case 8:
2153         case 10:
2154         case 11:
2155         case 12:
2156         case 13:
2157         case 14:
2158         case 15:
2159         case 18:
2160         case 19:
2161           /* Castling legal iff K & R start in normal positions */
2162           v = VariantNormal;
2163           break;
2164         case 6:
2165         case 20:
2166         case 21:
2167           /* Special wilds for position setup; unclear what to do here */
2168           v = VariantLoadable;
2169           break;
2170         case 9:
2171           /* Bizarre ICC game */
2172           v = VariantTwoKings;
2173           break;
2174         case 16:
2175           v = VariantKriegspiel;
2176           break;
2177         case 17:
2178           v = VariantLosers;
2179           break;
2180         case 22:
2181           v = VariantFischeRandom;
2182           break;
2183         case 23:
2184           v = VariantCrazyhouse;
2185           break;
2186         case 24:
2187           v = VariantBughouse;
2188           break;
2189         case 25:
2190           v = Variant3Check;
2191           break;
2192         case 26:
2193           /* Not quite the same as FICS suicide! */
2194           v = VariantGiveaway;
2195           break;
2196         case 27:
2197           v = VariantAtomic;
2198           break;
2199         case 28:
2200           v = VariantShatranj;
2201           break;
2202
2203         /* Temporary names for future ICC types.  The name *will* change in
2204            the next xboard/WinBoard release after ICC defines it. */
2205         case 29:
2206           v = Variant29;
2207           break;
2208         case 30:
2209           v = Variant30;
2210           break;
2211         case 31:
2212           v = Variant31;
2213           break;
2214         case 32:
2215           v = Variant32;
2216           break;
2217         case 33:
2218           v = Variant33;
2219           break;
2220         case 34:
2221           v = Variant34;
2222           break;
2223         case 35:
2224           v = Variant35;
2225           break;
2226         case 36:
2227           v = Variant36;
2228           break;
2229         case 37:
2230           v = VariantShogi;
2231           break;
2232         case 38:
2233           v = VariantXiangqi;
2234           break;
2235         case 39:
2236           v = VariantCourier;
2237           break;
2238         case 40:
2239           v = VariantGothic;
2240           break;
2241         case 41:
2242           v = VariantCapablanca;
2243           break;
2244         case 42:
2245           v = VariantKnightmate;
2246           break;
2247         case 43:
2248           v = VariantFairy;
2249           break;
2250         case 44:
2251           v = VariantCylinder;
2252           break;
2253         case 45:
2254           v = VariantFalcon;
2255           break;
2256         case 46:
2257           v = VariantCapaRandom;
2258           break;
2259         case 47:
2260           v = VariantBerolina;
2261           break;
2262         case 48:
2263           v = VariantJanus;
2264           break;
2265         case 49:
2266           v = VariantSuper;
2267           break;
2268         case 50:
2269           v = VariantGreat;
2270           break;
2271         case -1:
2272           /* Found "wild" or "w" in the string but no number;
2273              must assume it's normal chess. */
2274           v = VariantNormal;
2275           break;
2276         default:
2277           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2278           if( (len >= MSG_SIZ) && appData.debugMode )
2279             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2280
2281           DisplayError(buf, 0);
2282           v = VariantUnknown;
2283           break;
2284         }
2285       }
2286     }
2287     if (appData.debugMode) {
2288       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2289               e, wnum, VariantName(v));
2290     }
2291     return v;
2292 }
2293
2294 static int leftover_start = 0, leftover_len = 0;
2295 char star_match[STAR_MATCH_N][MSG_SIZ];
2296
2297 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2298    advance *index beyond it, and set leftover_start to the new value of
2299    *index; else return FALSE.  If pattern contains the character '*', it
2300    matches any sequence of characters not containing '\r', '\n', or the
2301    character following the '*' (if any), and the matched sequence(s) are
2302    copied into star_match.
2303    */
2304 int
2305 looking_at ( char *buf, int *index, char *pattern)
2306 {
2307     char *bufp = &buf[*index], *patternp = pattern;
2308     int star_count = 0;
2309     char *matchp = star_match[0];
2310
2311     for (;;) {
2312         if (*patternp == NULLCHAR) {
2313             *index = leftover_start = bufp - buf;
2314             *matchp = NULLCHAR;
2315             return TRUE;
2316         }
2317         if (*bufp == NULLCHAR) return FALSE;
2318         if (*patternp == '*') {
2319             if (*bufp == *(patternp + 1)) {
2320                 *matchp = NULLCHAR;
2321                 matchp = star_match[++star_count];
2322                 patternp += 2;
2323                 bufp++;
2324                 continue;
2325             } else if (*bufp == '\n' || *bufp == '\r') {
2326                 patternp++;
2327                 if (*patternp == NULLCHAR)
2328                   continue;
2329                 else
2330                   return FALSE;
2331             } else {
2332                 *matchp++ = *bufp++;
2333                 continue;
2334             }
2335         }
2336         if (*patternp != *bufp) return FALSE;
2337         patternp++;
2338         bufp++;
2339     }
2340 }
2341
2342 void
2343 SendToPlayer (char *data, int length)
2344 {
2345     int error, outCount;
2346     outCount = OutputToProcess(NoProc, data, length, &error);
2347     if (outCount < length) {
2348         DisplayFatalError(_("Error writing to display"), error, 1);
2349     }
2350 }
2351
2352 void
2353 PackHolding (char packed[], char *holding)
2354 {
2355     char *p = holding;
2356     char *q = packed;
2357     int runlength = 0;
2358     int curr = 9999;
2359     do {
2360         if (*p == curr) {
2361             runlength++;
2362         } else {
2363             switch (runlength) {
2364               case 0:
2365                 break;
2366               case 1:
2367                 *q++ = curr;
2368                 break;
2369               case 2:
2370                 *q++ = curr;
2371                 *q++ = curr;
2372                 break;
2373               default:
2374                 sprintf(q, "%d", runlength);
2375                 while (*q) q++;
2376                 *q++ = curr;
2377                 break;
2378             }
2379             runlength = 1;
2380             curr = *p;
2381         }
2382     } while (*p++);
2383     *q = NULLCHAR;
2384 }
2385
2386 /* Telnet protocol requests from the front end */
2387 void
2388 TelnetRequest (unsigned char ddww, unsigned char option)
2389 {
2390     unsigned char msg[3];
2391     int outCount, outError;
2392
2393     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2394
2395     if (appData.debugMode) {
2396         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2397         switch (ddww) {
2398           case TN_DO:
2399             ddwwStr = "DO";
2400             break;
2401           case TN_DONT:
2402             ddwwStr = "DONT";
2403             break;
2404           case TN_WILL:
2405             ddwwStr = "WILL";
2406             break;
2407           case TN_WONT:
2408             ddwwStr = "WONT";
2409             break;
2410           default:
2411             ddwwStr = buf1;
2412             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2413             break;
2414         }
2415         switch (option) {
2416           case TN_ECHO:
2417             optionStr = "ECHO";
2418             break;
2419           default:
2420             optionStr = buf2;
2421             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2422             break;
2423         }
2424         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2425     }
2426     msg[0] = TN_IAC;
2427     msg[1] = ddww;
2428     msg[2] = option;
2429     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2430     if (outCount < 3) {
2431         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2432     }
2433 }
2434
2435 void
2436 DoEcho ()
2437 {
2438     if (!appData.icsActive) return;
2439     TelnetRequest(TN_DO, TN_ECHO);
2440 }
2441
2442 void
2443 DontEcho ()
2444 {
2445     if (!appData.icsActive) return;
2446     TelnetRequest(TN_DONT, TN_ECHO);
2447 }
2448
2449 void
2450 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2451 {
2452     /* put the holdings sent to us by the server on the board holdings area */
2453     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2454     char p;
2455     ChessSquare piece;
2456
2457     if(gameInfo.holdingsWidth < 2)  return;
2458     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2459         return; // prevent overwriting by pre-board holdings
2460
2461     if( (int)lowestPiece >= BlackPawn ) {
2462         holdingsColumn = 0;
2463         countsColumn = 1;
2464         holdingsStartRow = BOARD_HEIGHT-1;
2465         direction = -1;
2466     } else {
2467         holdingsColumn = BOARD_WIDTH-1;
2468         countsColumn = BOARD_WIDTH-2;
2469         holdingsStartRow = 0;
2470         direction = 1;
2471     }
2472
2473     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2474         board[i][holdingsColumn] = EmptySquare;
2475         board[i][countsColumn]   = (ChessSquare) 0;
2476     }
2477     while( (p=*holdings++) != NULLCHAR ) {
2478         piece = CharToPiece( ToUpper(p) );
2479         if(piece == EmptySquare) continue;
2480         /*j = (int) piece - (int) WhitePawn;*/
2481         j = PieceToNumber(piece);
2482         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2483         if(j < 0) continue;               /* should not happen */
2484         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2485         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2486         board[holdingsStartRow+j*direction][countsColumn]++;
2487     }
2488 }
2489
2490
2491 void
2492 VariantSwitch (Board board, VariantClass newVariant)
2493 {
2494    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2495    static Board oldBoard;
2496
2497    startedFromPositionFile = FALSE;
2498    if(gameInfo.variant == newVariant) return;
2499
2500    /* [HGM] This routine is called each time an assignment is made to
2501     * gameInfo.variant during a game, to make sure the board sizes
2502     * are set to match the new variant. If that means adding or deleting
2503     * holdings, we shift the playing board accordingly
2504     * This kludge is needed because in ICS observe mode, we get boards
2505     * of an ongoing game without knowing the variant, and learn about the
2506     * latter only later. This can be because of the move list we requested,
2507     * in which case the game history is refilled from the beginning anyway,
2508     * but also when receiving holdings of a crazyhouse game. In the latter
2509     * case we want to add those holdings to the already received position.
2510     */
2511
2512
2513    if (appData.debugMode) {
2514      fprintf(debugFP, "Switch board from %s to %s\n",
2515              VariantName(gameInfo.variant), VariantName(newVariant));
2516      setbuf(debugFP, NULL);
2517    }
2518    shuffleOpenings = 0;       /* [HGM] shuffle */
2519    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2520    switch(newVariant)
2521      {
2522      case VariantShogi:
2523        newWidth = 9;  newHeight = 9;
2524        gameInfo.holdingsSize = 7;
2525      case VariantBughouse:
2526      case VariantCrazyhouse:
2527        newHoldingsWidth = 2; break;
2528      case VariantGreat:
2529        newWidth = 10;
2530      case VariantSuper:
2531        newHoldingsWidth = 2;
2532        gameInfo.holdingsSize = 8;
2533        break;
2534      case VariantGothic:
2535      case VariantCapablanca:
2536      case VariantCapaRandom:
2537        newWidth = 10;
2538      default:
2539        newHoldingsWidth = gameInfo.holdingsSize = 0;
2540      };
2541
2542    if(newWidth  != gameInfo.boardWidth  ||
2543       newHeight != gameInfo.boardHeight ||
2544       newHoldingsWidth != gameInfo.holdingsWidth ) {
2545
2546      /* shift position to new playing area, if needed */
2547      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2548        for(i=0; i<BOARD_HEIGHT; i++)
2549          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2550            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2551              board[i][j];
2552        for(i=0; i<newHeight; i++) {
2553          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2554          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2555        }
2556      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2557        for(i=0; i<BOARD_HEIGHT; i++)
2558          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2559            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2560              board[i][j];
2561      }
2562      board[HOLDINGS_SET] = 0;
2563      gameInfo.boardWidth  = newWidth;
2564      gameInfo.boardHeight = newHeight;
2565      gameInfo.holdingsWidth = newHoldingsWidth;
2566      gameInfo.variant = newVariant;
2567      InitDrawingSizes(-2, 0);
2568    } else gameInfo.variant = newVariant;
2569    CopyBoard(oldBoard, board);   // remember correctly formatted board
2570      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2571    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2572 }
2573
2574 static int loggedOn = FALSE;
2575
2576 /*-- Game start info cache: --*/
2577 int gs_gamenum;
2578 char gs_kind[MSG_SIZ];
2579 static char player1Name[128] = "";
2580 static char player2Name[128] = "";
2581 static char cont_seq[] = "\n\\   ";
2582 static int player1Rating = -1;
2583 static int player2Rating = -1;
2584 /*----------------------------*/
2585
2586 ColorClass curColor = ColorNormal;
2587 int suppressKibitz = 0;
2588
2589 // [HGM] seekgraph
2590 Boolean soughtPending = FALSE;
2591 Boolean seekGraphUp;
2592 #define MAX_SEEK_ADS 200
2593 #define SQUARE 0x80
2594 char *seekAdList[MAX_SEEK_ADS];
2595 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2596 float tcList[MAX_SEEK_ADS];
2597 char colorList[MAX_SEEK_ADS];
2598 int nrOfSeekAds = 0;
2599 int minRating = 1010, maxRating = 2800;
2600 int hMargin = 10, vMargin = 20, h, w;
2601 extern int squareSize, lineGap;
2602
2603 void
2604 PlotSeekAd (int i)
2605 {
2606         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2607         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2608         if(r < minRating+100 && r >=0 ) r = minRating+100;
2609         if(r > maxRating) r = maxRating;
2610         if(tc < 1.f) tc = 1.f;
2611         if(tc > 95.f) tc = 95.f;
2612         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2613         y = ((double)r - minRating)/(maxRating - minRating)
2614             * (h-vMargin-squareSize/8-1) + vMargin;
2615         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2616         if(strstr(seekAdList[i], " u ")) color = 1;
2617         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2618            !strstr(seekAdList[i], "bullet") &&
2619            !strstr(seekAdList[i], "blitz") &&
2620            !strstr(seekAdList[i], "standard") ) color = 2;
2621         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2622         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2623 }
2624
2625 void
2626 PlotSingleSeekAd (int i)
2627 {
2628         PlotSeekAd(i);
2629 }
2630
2631 void
2632 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2633 {
2634         char buf[MSG_SIZ], *ext = "";
2635         VariantClass v = StringToVariant(type);
2636         if(strstr(type, "wild")) {
2637             ext = type + 4; // append wild number
2638             if(v == VariantFischeRandom) type = "chess960"; else
2639             if(v == VariantLoadable) type = "setup"; else
2640             type = VariantName(v);
2641         }
2642         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2643         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2644             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2645             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2646             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2647             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2648             seekNrList[nrOfSeekAds] = nr;
2649             zList[nrOfSeekAds] = 0;
2650             seekAdList[nrOfSeekAds++] = StrSave(buf);
2651             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2652         }
2653 }
2654
2655 void
2656 EraseSeekDot (int i)
2657 {
2658     int x = xList[i], y = yList[i], d=squareSize/4, k;
2659     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2660     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2661     // now replot every dot that overlapped
2662     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2663         int xx = xList[k], yy = yList[k];
2664         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2665             DrawSeekDot(xx, yy, colorList[k]);
2666     }
2667 }
2668
2669 void
2670 RemoveSeekAd (int nr)
2671 {
2672         int i;
2673         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2674             EraseSeekDot(i);
2675             if(seekAdList[i]) free(seekAdList[i]);
2676             seekAdList[i] = seekAdList[--nrOfSeekAds];
2677             seekNrList[i] = seekNrList[nrOfSeekAds];
2678             ratingList[i] = ratingList[nrOfSeekAds];
2679             colorList[i]  = colorList[nrOfSeekAds];
2680             tcList[i] = tcList[nrOfSeekAds];
2681             xList[i]  = xList[nrOfSeekAds];
2682             yList[i]  = yList[nrOfSeekAds];
2683             zList[i]  = zList[nrOfSeekAds];
2684             seekAdList[nrOfSeekAds] = NULL;
2685             break;
2686         }
2687 }
2688
2689 Boolean
2690 MatchSoughtLine (char *line)
2691 {
2692     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2693     int nr, base, inc, u=0; char dummy;
2694
2695     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2696        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2697        (u=1) &&
2698        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2699         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2700         // match: compact and save the line
2701         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2702         return TRUE;
2703     }
2704     return FALSE;
2705 }
2706
2707 int
2708 DrawSeekGraph ()
2709 {
2710     int i;
2711     if(!seekGraphUp) return FALSE;
2712     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2713     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2714
2715     DrawSeekBackground(0, 0, w, h);
2716     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2717     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2718     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2719         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2720         yy = h-1-yy;
2721         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2722         if(i%500 == 0) {
2723             char buf[MSG_SIZ];
2724             snprintf(buf, MSG_SIZ, "%d", i);
2725             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2726         }
2727     }
2728     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2729     for(i=1; i<100; i+=(i<10?1:5)) {
2730         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2731         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2732         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2733             char buf[MSG_SIZ];
2734             snprintf(buf, MSG_SIZ, "%d", i);
2735             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2736         }
2737     }
2738     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2739     return TRUE;
2740 }
2741
2742 int
2743 SeekGraphClick (ClickType click, int x, int y, int moving)
2744 {
2745     static int lastDown = 0, displayed = 0, lastSecond;
2746     if(y < 0) return FALSE;
2747     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2748         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2749         if(!seekGraphUp) return FALSE;
2750         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2751         DrawPosition(TRUE, NULL);
2752         return TRUE;
2753     }
2754     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2755         if(click == Release || moving) return FALSE;
2756         nrOfSeekAds = 0;
2757         soughtPending = TRUE;
2758         SendToICS(ics_prefix);
2759         SendToICS("sought\n"); // should this be "sought all"?
2760     } else { // issue challenge based on clicked ad
2761         int dist = 10000; int i, closest = 0, second = 0;
2762         for(i=0; i<nrOfSeekAds; i++) {
2763             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2764             if(d < dist) { dist = d; closest = i; }
2765             second += (d - zList[i] < 120); // count in-range ads
2766             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2767         }
2768         if(dist < 120) {
2769             char buf[MSG_SIZ];
2770             second = (second > 1);
2771             if(displayed != closest || second != lastSecond) {
2772                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2773                 lastSecond = second; displayed = closest;
2774             }
2775             if(click == Press) {
2776                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2777                 lastDown = closest;
2778                 return TRUE;
2779             } // on press 'hit', only show info
2780             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2781             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2782             SendToICS(ics_prefix);
2783             SendToICS(buf);
2784             return TRUE; // let incoming board of started game pop down the graph
2785         } else if(click == Release) { // release 'miss' is ignored
2786             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2787             if(moving == 2) { // right up-click
2788                 nrOfSeekAds = 0; // refresh graph
2789                 soughtPending = TRUE;
2790                 SendToICS(ics_prefix);
2791                 SendToICS("sought\n"); // should this be "sought all"?
2792             }
2793             return TRUE;
2794         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2795         // press miss or release hit 'pop down' seek graph
2796         seekGraphUp = FALSE;
2797         DrawPosition(TRUE, NULL);
2798     }
2799     return TRUE;
2800 }
2801
2802 void
2803 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2804 {
2805 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2806 #define STARTED_NONE 0
2807 #define STARTED_MOVES 1
2808 #define STARTED_BOARD 2
2809 #define STARTED_OBSERVE 3
2810 #define STARTED_HOLDINGS 4
2811 #define STARTED_CHATTER 5
2812 #define STARTED_COMMENT 6
2813 #define STARTED_MOVES_NOHIDE 7
2814
2815     static int started = STARTED_NONE;
2816     static char parse[20000];
2817     static int parse_pos = 0;
2818     static char buf[BUF_SIZE + 1];
2819     static int firstTime = TRUE, intfSet = FALSE;
2820     static ColorClass prevColor = ColorNormal;
2821     static int savingComment = FALSE;
2822     static int cmatch = 0; // continuation sequence match
2823     char *bp;
2824     char str[MSG_SIZ];
2825     int i, oldi;
2826     int buf_len;
2827     int next_out;
2828     int tkind;
2829     int backup;    /* [DM] For zippy color lines */
2830     char *p;
2831     char talker[MSG_SIZ]; // [HGM] chat
2832     int channel;
2833
2834     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2835
2836     if (appData.debugMode) {
2837       if (!error) {
2838         fprintf(debugFP, "<ICS: ");
2839         show_bytes(debugFP, data, count);
2840         fprintf(debugFP, "\n");
2841       }
2842     }
2843
2844     if (appData.debugMode) { int f = forwardMostMove;
2845         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2846                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2847                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2848     }
2849     if (count > 0) {
2850         /* If last read ended with a partial line that we couldn't parse,
2851            prepend it to the new read and try again. */
2852         if (leftover_len > 0) {
2853             for (i=0; i<leftover_len; i++)
2854               buf[i] = buf[leftover_start + i];
2855         }
2856
2857     /* copy new characters into the buffer */
2858     bp = buf + leftover_len;
2859     buf_len=leftover_len;
2860     for (i=0; i<count; i++)
2861     {
2862         // ignore these
2863         if (data[i] == '\r')
2864             continue;
2865
2866         // join lines split by ICS?
2867         if (!appData.noJoin)
2868         {
2869             /*
2870                 Joining just consists of finding matches against the
2871                 continuation sequence, and discarding that sequence
2872                 if found instead of copying it.  So, until a match
2873                 fails, there's nothing to do since it might be the
2874                 complete sequence, and thus, something we don't want
2875                 copied.
2876             */
2877             if (data[i] == cont_seq[cmatch])
2878             {
2879                 cmatch++;
2880                 if (cmatch == strlen(cont_seq))
2881                 {
2882                     cmatch = 0; // complete match.  just reset the counter
2883
2884                     /*
2885                         it's possible for the ICS to not include the space
2886                         at the end of the last word, making our [correct]
2887                         join operation fuse two separate words.  the server
2888                         does this when the space occurs at the width setting.
2889                     */
2890                     if (!buf_len || buf[buf_len-1] != ' ')
2891                     {
2892                         *bp++ = ' ';
2893                         buf_len++;
2894                     }
2895                 }
2896                 continue;
2897             }
2898             else if (cmatch)
2899             {
2900                 /*
2901                     match failed, so we have to copy what matched before
2902                     falling through and copying this character.  In reality,
2903                     this will only ever be just the newline character, but
2904                     it doesn't hurt to be precise.
2905                 */
2906                 strncpy(bp, cont_seq, cmatch);
2907                 bp += cmatch;
2908                 buf_len += cmatch;
2909                 cmatch = 0;
2910             }
2911         }
2912
2913         // copy this char
2914         *bp++ = data[i];
2915         buf_len++;
2916     }
2917
2918         buf[buf_len] = NULLCHAR;
2919 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2920         next_out = 0;
2921         leftover_start = 0;
2922
2923         i = 0;
2924         while (i < buf_len) {
2925             /* Deal with part of the TELNET option negotiation
2926                protocol.  We refuse to do anything beyond the
2927                defaults, except that we allow the WILL ECHO option,
2928                which ICS uses to turn off password echoing when we are
2929                directly connected to it.  We reject this option
2930                if localLineEditing mode is on (always on in xboard)
2931                and we are talking to port 23, which might be a real
2932                telnet server that will try to keep WILL ECHO on permanently.
2933              */
2934             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2935                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2936                 unsigned char option;
2937                 oldi = i;
2938                 switch ((unsigned char) buf[++i]) {
2939                   case TN_WILL:
2940                     if (appData.debugMode)
2941                       fprintf(debugFP, "\n<WILL ");
2942                     switch (option = (unsigned char) buf[++i]) {
2943                       case TN_ECHO:
2944                         if (appData.debugMode)
2945                           fprintf(debugFP, "ECHO ");
2946                         /* Reply only if this is a change, according
2947                            to the protocol rules. */
2948                         if (remoteEchoOption) break;
2949                         if (appData.localLineEditing &&
2950                             atoi(appData.icsPort) == TN_PORT) {
2951                             TelnetRequest(TN_DONT, TN_ECHO);
2952                         } else {
2953                             EchoOff();
2954                             TelnetRequest(TN_DO, TN_ECHO);
2955                             remoteEchoOption = TRUE;
2956                         }
2957                         break;
2958                       default:
2959                         if (appData.debugMode)
2960                           fprintf(debugFP, "%d ", option);
2961                         /* Whatever this is, we don't want it. */
2962                         TelnetRequest(TN_DONT, option);
2963                         break;
2964                     }
2965                     break;
2966                   case TN_WONT:
2967                     if (appData.debugMode)
2968                       fprintf(debugFP, "\n<WONT ");
2969                     switch (option = (unsigned char) buf[++i]) {
2970                       case TN_ECHO:
2971                         if (appData.debugMode)
2972                           fprintf(debugFP, "ECHO ");
2973                         /* Reply only if this is a change, according
2974                            to the protocol rules. */
2975                         if (!remoteEchoOption) break;
2976                         EchoOn();
2977                         TelnetRequest(TN_DONT, TN_ECHO);
2978                         remoteEchoOption = FALSE;
2979                         break;
2980                       default:
2981                         if (appData.debugMode)
2982                           fprintf(debugFP, "%d ", (unsigned char) option);
2983                         /* Whatever this is, it must already be turned
2984                            off, because we never agree to turn on
2985                            anything non-default, so according to the
2986                            protocol rules, we don't reply. */
2987                         break;
2988                     }
2989                     break;
2990                   case TN_DO:
2991                     if (appData.debugMode)
2992                       fprintf(debugFP, "\n<DO ");
2993                     switch (option = (unsigned char) buf[++i]) {
2994                       default:
2995                         /* Whatever this is, we refuse to do it. */
2996                         if (appData.debugMode)
2997                           fprintf(debugFP, "%d ", option);
2998                         TelnetRequest(TN_WONT, option);
2999                         break;
3000                     }
3001                     break;
3002                   case TN_DONT:
3003                     if (appData.debugMode)
3004                       fprintf(debugFP, "\n<DONT ");
3005                     switch (option = (unsigned char) buf[++i]) {
3006                       default:
3007                         if (appData.debugMode)
3008                           fprintf(debugFP, "%d ", option);
3009                         /* Whatever this is, we are already not doing
3010                            it, because we never agree to do anything
3011                            non-default, so according to the protocol
3012                            rules, we don't reply. */
3013                         break;
3014                     }
3015                     break;
3016                   case TN_IAC:
3017                     if (appData.debugMode)
3018                       fprintf(debugFP, "\n<IAC ");
3019                     /* Doubled IAC; pass it through */
3020                     i--;
3021                     break;
3022                   default:
3023                     if (appData.debugMode)
3024                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3025                     /* Drop all other telnet commands on the floor */
3026                     break;
3027                 }
3028                 if (oldi > next_out)
3029                   SendToPlayer(&buf[next_out], oldi - next_out);
3030                 if (++i > next_out)
3031                   next_out = i;
3032                 continue;
3033             }
3034
3035             /* OK, this at least will *usually* work */
3036             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3037                 loggedOn = TRUE;
3038             }
3039
3040             if (loggedOn && !intfSet) {
3041                 if (ics_type == ICS_ICC) {
3042                   snprintf(str, MSG_SIZ,
3043                           "/set-quietly interface %s\n/set-quietly style 12\n",
3044                           programVersion);
3045                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3046                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3047                 } else if (ics_type == ICS_CHESSNET) {
3048                   snprintf(str, MSG_SIZ, "/style 12\n");
3049                 } else {
3050                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3051                   strcat(str, programVersion);
3052                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3053                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3054                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3055 #ifdef WIN32
3056                   strcat(str, "$iset nohighlight 1\n");
3057 #endif
3058                   strcat(str, "$iset lock 1\n$style 12\n");
3059                 }
3060                 SendToICS(str);
3061                 NotifyFrontendLogin();
3062                 intfSet = TRUE;
3063             }
3064
3065             if (started == STARTED_COMMENT) {
3066                 /* Accumulate characters in comment */
3067                 parse[parse_pos++] = buf[i];
3068                 if (buf[i] == '\n') {
3069                     parse[parse_pos] = NULLCHAR;
3070                     if(chattingPartner>=0) {
3071                         char mess[MSG_SIZ];
3072                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3073                         OutputChatMessage(chattingPartner, mess);
3074                         chattingPartner = -1;
3075                         next_out = i+1; // [HGM] suppress printing in ICS window
3076                     } else
3077                     if(!suppressKibitz) // [HGM] kibitz
3078                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3079                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3080                         int nrDigit = 0, nrAlph = 0, j;
3081                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3082                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3083                         parse[parse_pos] = NULLCHAR;
3084                         // try to be smart: if it does not look like search info, it should go to
3085                         // ICS interaction window after all, not to engine-output window.
3086                         for(j=0; j<parse_pos; j++) { // count letters and digits
3087                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3088                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3089                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3090                         }
3091                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3092                             int depth=0; float score;
3093                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3094                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3095                                 pvInfoList[forwardMostMove-1].depth = depth;
3096                                 pvInfoList[forwardMostMove-1].score = 100*score;
3097                             }
3098                             OutputKibitz(suppressKibitz, parse);
3099                         } else {
3100                             char tmp[MSG_SIZ];
3101                             if(gameMode == IcsObserving) // restore original ICS messages
3102                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3103                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3104                             else
3105                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3106                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3107                             SendToPlayer(tmp, strlen(tmp));
3108                         }
3109                         next_out = i+1; // [HGM] suppress printing in ICS window
3110                     }
3111                     started = STARTED_NONE;
3112                 } else {
3113                     /* Don't match patterns against characters in comment */
3114                     i++;
3115                     continue;
3116                 }
3117             }
3118             if (started == STARTED_CHATTER) {
3119                 if (buf[i] != '\n') {
3120                     /* Don't match patterns against characters in chatter */
3121                     i++;
3122                     continue;
3123                 }
3124                 started = STARTED_NONE;
3125                 if(suppressKibitz) next_out = i+1;
3126             }
3127
3128             /* Kludge to deal with rcmd protocol */
3129             if (firstTime && looking_at(buf, &i, "\001*")) {
3130                 DisplayFatalError(&buf[1], 0, 1);
3131                 continue;
3132             } else {
3133                 firstTime = FALSE;
3134             }
3135
3136             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3137                 ics_type = ICS_ICC;
3138                 ics_prefix = "/";
3139                 if (appData.debugMode)
3140                   fprintf(debugFP, "ics_type %d\n", ics_type);
3141                 continue;
3142             }
3143             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3144                 ics_type = ICS_FICS;
3145                 ics_prefix = "$";
3146                 if (appData.debugMode)
3147                   fprintf(debugFP, "ics_type %d\n", ics_type);
3148                 continue;
3149             }
3150             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3151                 ics_type = ICS_CHESSNET;
3152                 ics_prefix = "/";
3153                 if (appData.debugMode)
3154                   fprintf(debugFP, "ics_type %d\n", ics_type);
3155                 continue;
3156             }
3157
3158             if (!loggedOn &&
3159                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3160                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3161                  looking_at(buf, &i, "will be \"*\""))) {
3162               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3163               continue;
3164             }
3165
3166             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3167               char buf[MSG_SIZ];
3168               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3169               DisplayIcsInteractionTitle(buf);
3170               have_set_title = TRUE;
3171             }
3172
3173             /* skip finger notes */
3174             if (started == STARTED_NONE &&
3175                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3176                  (buf[i] == '1' && buf[i+1] == '0')) &&
3177                 buf[i+2] == ':' && buf[i+3] == ' ') {
3178               started = STARTED_CHATTER;
3179               i += 3;
3180               continue;
3181             }
3182
3183             oldi = i;
3184             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3185             if(appData.seekGraph) {
3186                 if(soughtPending && MatchSoughtLine(buf+i)) {
3187                     i = strstr(buf+i, "rated") - buf;
3188                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3189                     next_out = leftover_start = i;
3190                     started = STARTED_CHATTER;
3191                     suppressKibitz = TRUE;
3192                     continue;
3193                 }
3194                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3195                         && looking_at(buf, &i, "* ads displayed")) {
3196                     soughtPending = FALSE;
3197                     seekGraphUp = TRUE;
3198                     DrawSeekGraph();
3199                     continue;
3200                 }
3201                 if(appData.autoRefresh) {
3202                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3203                         int s = (ics_type == ICS_ICC); // ICC format differs
3204                         if(seekGraphUp)
3205                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3206                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3207                         looking_at(buf, &i, "*% "); // eat prompt
3208                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3209                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3210                         next_out = i; // suppress
3211                         continue;
3212                     }
3213                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3214                         char *p = star_match[0];
3215                         while(*p) {
3216                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3217                             while(*p && *p++ != ' '); // next
3218                         }
3219                         looking_at(buf, &i, "*% "); // eat prompt
3220                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3221                         next_out = i;
3222                         continue;
3223                     }
3224                 }
3225             }
3226
3227             /* skip formula vars */
3228             if (started == STARTED_NONE &&
3229                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3230               started = STARTED_CHATTER;
3231               i += 3;
3232               continue;
3233             }
3234
3235             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3236             if (appData.autoKibitz && started == STARTED_NONE &&
3237                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3238                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3239                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3240                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3241                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3242                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3243                         suppressKibitz = TRUE;
3244                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3245                         next_out = i;
3246                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3247                                 && (gameMode == IcsPlayingWhite)) ||
3248                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3249                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3250                             started = STARTED_CHATTER; // own kibitz we simply discard
3251                         else {
3252                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3253                             parse_pos = 0; parse[0] = NULLCHAR;
3254                             savingComment = TRUE;
3255                             suppressKibitz = gameMode != IcsObserving ? 2 :
3256                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3257                         }
3258                         continue;
3259                 } else
3260                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3261                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3262                          && atoi(star_match[0])) {
3263                     // suppress the acknowledgements of our own autoKibitz
3264                     char *p;
3265                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3266                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3267                     SendToPlayer(star_match[0], strlen(star_match[0]));
3268                     if(looking_at(buf, &i, "*% ")) // eat prompt
3269                         suppressKibitz = FALSE;
3270                     next_out = i;
3271                     continue;
3272                 }
3273             } // [HGM] kibitz: end of patch
3274
3275             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3276
3277             // [HGM] chat: intercept tells by users for which we have an open chat window
3278             channel = -1;
3279             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3280                                            looking_at(buf, &i, "* whispers:") ||
3281                                            looking_at(buf, &i, "* kibitzes:") ||
3282                                            looking_at(buf, &i, "* shouts:") ||
3283                                            looking_at(buf, &i, "* c-shouts:") ||
3284                                            looking_at(buf, &i, "--> * ") ||
3285                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3286                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3287                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3288                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3289                 int p;
3290                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3291                 chattingPartner = -1;
3292
3293                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3294                 for(p=0; p<MAX_CHAT; p++) {
3295                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3296                     talker[0] = '['; strcat(talker, "] ");
3297                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3298                     chattingPartner = p; break;
3299                     }
3300                 } else
3301                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3302                 for(p=0; p<MAX_CHAT; p++) {
3303                     if(!strcmp("kibitzes", chatPartner[p])) {
3304                         talker[0] = '['; strcat(talker, "] ");
3305                         chattingPartner = p; break;
3306                     }
3307                 } else
3308                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3309                 for(p=0; p<MAX_CHAT; p++) {
3310                     if(!strcmp("whispers", chatPartner[p])) {
3311                         talker[0] = '['; strcat(talker, "] ");
3312                         chattingPartner = p; break;
3313                     }
3314                 } else
3315                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3316                   if(buf[i-8] == '-' && buf[i-3] == 't')
3317                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3318                     if(!strcmp("c-shouts", chatPartner[p])) {
3319                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3320                         chattingPartner = p; break;
3321                     }
3322                   }
3323                   if(chattingPartner < 0)
3324                   for(p=0; p<MAX_CHAT; p++) {
3325                     if(!strcmp("shouts", chatPartner[p])) {
3326                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3327                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3328                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3329                         chattingPartner = p; break;
3330                     }
3331                   }
3332                 }
3333                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3334                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3335                     talker[0] = 0; Colorize(ColorTell, FALSE);
3336                     chattingPartner = p; break;
3337                 }
3338                 if(chattingPartner<0) i = oldi; else {
3339                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3340                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3341                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3342                     started = STARTED_COMMENT;
3343                     parse_pos = 0; parse[0] = NULLCHAR;
3344                     savingComment = 3 + chattingPartner; // counts as TRUE
3345                     suppressKibitz = TRUE;
3346                     continue;
3347                 }
3348             } // [HGM] chat: end of patch
3349
3350           backup = i;
3351             if (appData.zippyTalk || appData.zippyPlay) {
3352                 /* [DM] Backup address for color zippy lines */
3353 #if ZIPPY
3354                if (loggedOn == TRUE)
3355                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3356                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3357 #endif
3358             } // [DM] 'else { ' deleted
3359                 if (
3360                     /* Regular tells and says */
3361                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3362                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3363                     looking_at(buf, &i, "* says: ") ||
3364                     /* Don't color "message" or "messages" output */
3365                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3366                     looking_at(buf, &i, "*. * at *:*: ") ||
3367                     looking_at(buf, &i, "--* (*:*): ") ||
3368                     /* Message notifications (same color as tells) */
3369                     looking_at(buf, &i, "* has left a message ") ||
3370                     looking_at(buf, &i, "* just sent you a message:\n") ||
3371                     /* Whispers and kibitzes */
3372                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3373                     looking_at(buf, &i, "* kibitzes: ") ||
3374                     /* Channel tells */
3375                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3376
3377                   if (tkind == 1 && strchr(star_match[0], ':')) {
3378                       /* Avoid "tells you:" spoofs in channels */
3379                      tkind = 3;
3380                   }
3381                   if (star_match[0][0] == NULLCHAR ||
3382                       strchr(star_match[0], ' ') ||
3383                       (tkind == 3 && strchr(star_match[1], ' '))) {
3384                     /* Reject bogus matches */
3385                     i = oldi;
3386                   } else {
3387                     if (appData.colorize) {
3388                       if (oldi > next_out) {
3389                         SendToPlayer(&buf[next_out], oldi - next_out);
3390                         next_out = oldi;
3391                       }
3392                       switch (tkind) {
3393                       case 1:
3394                         Colorize(ColorTell, FALSE);
3395                         curColor = ColorTell;
3396                         break;
3397                       case 2:
3398                         Colorize(ColorKibitz, FALSE);
3399                         curColor = ColorKibitz;
3400                         break;
3401                       case 3:
3402                         p = strrchr(star_match[1], '(');
3403                         if (p == NULL) {
3404                           p = star_match[1];
3405                         } else {
3406                           p++;
3407                         }
3408                         if (atoi(p) == 1) {
3409                           Colorize(ColorChannel1, FALSE);
3410                           curColor = ColorChannel1;
3411                         } else {
3412                           Colorize(ColorChannel, FALSE);
3413                           curColor = ColorChannel;
3414                         }
3415                         break;
3416                       case 5:
3417                         curColor = ColorNormal;
3418                         break;
3419                       }
3420                     }
3421                     if (started == STARTED_NONE && appData.autoComment &&
3422                         (gameMode == IcsObserving ||
3423                          gameMode == IcsPlayingWhite ||
3424                          gameMode == IcsPlayingBlack)) {
3425                       parse_pos = i - oldi;
3426                       memcpy(parse, &buf[oldi], parse_pos);
3427                       parse[parse_pos] = NULLCHAR;
3428                       started = STARTED_COMMENT;
3429                       savingComment = TRUE;
3430                     } else {
3431                       started = STARTED_CHATTER;
3432                       savingComment = FALSE;
3433                     }
3434                     loggedOn = TRUE;
3435                     continue;
3436                   }
3437                 }
3438
3439                 if (looking_at(buf, &i, "* s-shouts: ") ||
3440                     looking_at(buf, &i, "* c-shouts: ")) {
3441                     if (appData.colorize) {
3442                         if (oldi > next_out) {
3443                             SendToPlayer(&buf[next_out], oldi - next_out);
3444                             next_out = oldi;
3445                         }
3446                         Colorize(ColorSShout, FALSE);
3447                         curColor = ColorSShout;
3448                     }
3449                     loggedOn = TRUE;
3450                     started = STARTED_CHATTER;
3451                     continue;
3452                 }
3453
3454                 if (looking_at(buf, &i, "--->")) {
3455                     loggedOn = TRUE;
3456                     continue;
3457                 }
3458
3459                 if (looking_at(buf, &i, "* shouts: ") ||
3460                     looking_at(buf, &i, "--> ")) {
3461                     if (appData.colorize) {
3462                         if (oldi > next_out) {
3463                             SendToPlayer(&buf[next_out], oldi - next_out);
3464                             next_out = oldi;
3465                         }
3466                         Colorize(ColorShout, FALSE);
3467                         curColor = ColorShout;
3468                     }
3469                     loggedOn = TRUE;
3470                     started = STARTED_CHATTER;
3471                     continue;
3472                 }
3473
3474                 if (looking_at( buf, &i, "Challenge:")) {
3475                     if (appData.colorize) {
3476                         if (oldi > next_out) {
3477                             SendToPlayer(&buf[next_out], oldi - next_out);
3478                             next_out = oldi;
3479                         }
3480                         Colorize(ColorChallenge, FALSE);
3481                         curColor = ColorChallenge;
3482                     }
3483                     loggedOn = TRUE;
3484                     continue;
3485                 }
3486
3487                 if (looking_at(buf, &i, "* offers you") ||
3488                     looking_at(buf, &i, "* offers to be") ||
3489                     looking_at(buf, &i, "* would like to") ||
3490                     looking_at(buf, &i, "* requests to") ||
3491                     looking_at(buf, &i, "Your opponent offers") ||
3492                     looking_at(buf, &i, "Your opponent requests")) {
3493
3494                     if (appData.colorize) {
3495                         if (oldi > next_out) {
3496                             SendToPlayer(&buf[next_out], oldi - next_out);
3497                             next_out = oldi;
3498                         }
3499                         Colorize(ColorRequest, FALSE);
3500                         curColor = ColorRequest;
3501                     }
3502                     continue;
3503                 }
3504
3505                 if (looking_at(buf, &i, "* (*) seeking")) {
3506                     if (appData.colorize) {
3507                         if (oldi > next_out) {
3508                             SendToPlayer(&buf[next_out], oldi - next_out);
3509                             next_out = oldi;
3510                         }
3511                         Colorize(ColorSeek, FALSE);
3512                         curColor = ColorSeek;
3513                     }
3514                     continue;
3515             }
3516
3517           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3518
3519             if (looking_at(buf, &i, "\\   ")) {
3520                 if (prevColor != ColorNormal) {
3521                     if (oldi > next_out) {
3522                         SendToPlayer(&buf[next_out], oldi - next_out);
3523                         next_out = oldi;
3524                     }
3525                     Colorize(prevColor, TRUE);
3526                     curColor = prevColor;
3527                 }
3528                 if (savingComment) {
3529                     parse_pos = i - oldi;
3530                     memcpy(parse, &buf[oldi], parse_pos);
3531                     parse[parse_pos] = NULLCHAR;
3532                     started = STARTED_COMMENT;
3533                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3534                         chattingPartner = savingComment - 3; // kludge to remember the box
3535                 } else {
3536                     started = STARTED_CHATTER;
3537                 }
3538                 continue;
3539             }
3540
3541             if (looking_at(buf, &i, "Black Strength :") ||
3542                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3543                 looking_at(buf, &i, "<10>") ||
3544                 looking_at(buf, &i, "#@#")) {
3545                 /* Wrong board style */
3546                 loggedOn = TRUE;
3547                 SendToICS(ics_prefix);
3548                 SendToICS("set style 12\n");
3549                 SendToICS(ics_prefix);
3550                 SendToICS("refresh\n");
3551                 continue;
3552             }
3553
3554             if (looking_at(buf, &i, "login:")) {
3555               if (!have_sent_ICS_logon) {
3556                 if(ICSInitScript())
3557                   have_sent_ICS_logon = 1;
3558                 else // no init script was found
3559                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3560               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3561                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3562               }
3563                 continue;
3564             }
3565
3566             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3567                 (looking_at(buf, &i, "\n<12> ") ||
3568                  looking_at(buf, &i, "<12> "))) {
3569                 loggedOn = TRUE;
3570                 if (oldi > next_out) {
3571                     SendToPlayer(&buf[next_out], oldi - next_out);
3572                 }
3573                 next_out = i;
3574                 started = STARTED_BOARD;
3575                 parse_pos = 0;
3576                 continue;
3577             }
3578
3579             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3580                 looking_at(buf, &i, "<b1> ")) {
3581                 if (oldi > next_out) {
3582                     SendToPlayer(&buf[next_out], oldi - next_out);
3583                 }
3584                 next_out = i;
3585                 started = STARTED_HOLDINGS;
3586                 parse_pos = 0;
3587                 continue;
3588             }
3589
3590             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3591                 loggedOn = TRUE;
3592                 /* Header for a move list -- first line */
3593
3594                 switch (ics_getting_history) {
3595                   case H_FALSE:
3596                     switch (gameMode) {
3597                       case IcsIdle:
3598                       case BeginningOfGame:
3599                         /* User typed "moves" or "oldmoves" while we
3600                            were idle.  Pretend we asked for these
3601                            moves and soak them up so user can step
3602                            through them and/or save them.
3603                            */
3604                         Reset(FALSE, TRUE);
3605                         gameMode = IcsObserving;
3606                         ModeHighlight();
3607                         ics_gamenum = -1;
3608                         ics_getting_history = H_GOT_UNREQ_HEADER;
3609                         break;
3610                       case EditGame: /*?*/
3611                       case EditPosition: /*?*/
3612                         /* Should above feature work in these modes too? */
3613                         /* For now it doesn't */
3614                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3615                         break;
3616                       default:
3617                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3618                         break;
3619                     }
3620                     break;
3621                   case H_REQUESTED:
3622                     /* Is this the right one? */
3623                     if (gameInfo.white && gameInfo.black &&
3624                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3625                         strcmp(gameInfo.black, star_match[2]) == 0) {
3626                         /* All is well */
3627                         ics_getting_history = H_GOT_REQ_HEADER;
3628                     }
3629                     break;
3630                   case H_GOT_REQ_HEADER:
3631                   case H_GOT_UNREQ_HEADER:
3632                   case H_GOT_UNWANTED_HEADER:
3633                   case H_GETTING_MOVES:
3634                     /* Should not happen */
3635                     DisplayError(_("Error gathering move list: two headers"), 0);
3636                     ics_getting_history = H_FALSE;
3637                     break;
3638                 }
3639
3640                 /* Save player ratings into gameInfo if needed */
3641                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3642                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3643                     (gameInfo.whiteRating == -1 ||
3644                      gameInfo.blackRating == -1)) {
3645
3646                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3647                     gameInfo.blackRating = string_to_rating(star_match[3]);
3648                     if (appData.debugMode)
3649                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3650                               gameInfo.whiteRating, gameInfo.blackRating);
3651                 }
3652                 continue;
3653             }
3654
3655             if (looking_at(buf, &i,
3656               "* * match, initial time: * minute*, increment: * second")) {
3657                 /* Header for a move list -- second line */
3658                 /* Initial board will follow if this is a wild game */
3659                 if (gameInfo.event != NULL) free(gameInfo.event);
3660                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3661                 gameInfo.event = StrSave(str);
3662                 /* [HGM] we switched variant. Translate boards if needed. */
3663                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3664                 continue;
3665             }
3666
3667             if (looking_at(buf, &i, "Move  ")) {
3668                 /* Beginning of a move list */
3669                 switch (ics_getting_history) {
3670                   case H_FALSE:
3671                     /* Normally should not happen */
3672                     /* Maybe user hit reset while we were parsing */
3673                     break;
3674                   case H_REQUESTED:
3675                     /* Happens if we are ignoring a move list that is not
3676                      * the one we just requested.  Common if the user
3677                      * tries to observe two games without turning off
3678                      * getMoveList */
3679                     break;
3680                   case H_GETTING_MOVES:
3681                     /* Should not happen */
3682                     DisplayError(_("Error gathering move list: nested"), 0);
3683                     ics_getting_history = H_FALSE;
3684                     break;
3685                   case H_GOT_REQ_HEADER:
3686                     ics_getting_history = H_GETTING_MOVES;
3687                     started = STARTED_MOVES;
3688                     parse_pos = 0;
3689                     if (oldi > next_out) {
3690                         SendToPlayer(&buf[next_out], oldi - next_out);
3691                     }
3692                     break;
3693                   case H_GOT_UNREQ_HEADER:
3694                     ics_getting_history = H_GETTING_MOVES;
3695                     started = STARTED_MOVES_NOHIDE;
3696                     parse_pos = 0;
3697                     break;
3698                   case H_GOT_UNWANTED_HEADER:
3699                     ics_getting_history = H_FALSE;
3700                     break;
3701                 }
3702                 continue;
3703             }
3704
3705             if (looking_at(buf, &i, "% ") ||
3706                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3707                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3708                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3709                     soughtPending = FALSE;
3710                     seekGraphUp = TRUE;
3711                     DrawSeekGraph();
3712                 }
3713                 if(suppressKibitz) next_out = i;
3714                 savingComment = FALSE;
3715                 suppressKibitz = 0;
3716                 switch (started) {
3717                   case STARTED_MOVES:
3718                   case STARTED_MOVES_NOHIDE:
3719                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3720                     parse[parse_pos + i - oldi] = NULLCHAR;
3721                     ParseGameHistory(parse);
3722 #if ZIPPY
3723                     if (appData.zippyPlay && first.initDone) {
3724                         FeedMovesToProgram(&first, forwardMostMove);
3725                         if (gameMode == IcsPlayingWhite) {
3726                             if (WhiteOnMove(forwardMostMove)) {
3727                                 if (first.sendTime) {
3728                                   if (first.useColors) {
3729                                     SendToProgram("black\n", &first);
3730                                   }
3731                                   SendTimeRemaining(&first, TRUE);
3732                                 }
3733                                 if (first.useColors) {
3734                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3735                                 }
3736                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3737                                 first.maybeThinking = TRUE;
3738                             } else {
3739                                 if (first.usePlayother) {
3740                                   if (first.sendTime) {
3741                                     SendTimeRemaining(&first, TRUE);
3742                                   }
3743                                   SendToProgram("playother\n", &first);
3744                                   firstMove = FALSE;
3745                                 } else {
3746                                   firstMove = TRUE;
3747                                 }
3748                             }
3749                         } else if (gameMode == IcsPlayingBlack) {
3750                             if (!WhiteOnMove(forwardMostMove)) {
3751                                 if (first.sendTime) {
3752                                   if (first.useColors) {
3753                                     SendToProgram("white\n", &first);
3754                                   }
3755                                   SendTimeRemaining(&first, FALSE);
3756                                 }
3757                                 if (first.useColors) {
3758                                   SendToProgram("black\n", &first);
3759                                 }
3760                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3761                                 first.maybeThinking = TRUE;
3762                             } else {
3763                                 if (first.usePlayother) {
3764                                   if (first.sendTime) {
3765                                     SendTimeRemaining(&first, FALSE);
3766                                   }
3767                                   SendToProgram("playother\n", &first);
3768                                   firstMove = FALSE;
3769                                 } else {
3770                                   firstMove = TRUE;
3771                                 }
3772                             }
3773                         }
3774                     }
3775 #endif
3776                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3777                         /* Moves came from oldmoves or moves command
3778                            while we weren't doing anything else.
3779                            */
3780                         currentMove = forwardMostMove;
3781                         ClearHighlights();/*!!could figure this out*/
3782                         flipView = appData.flipView;
3783                         DrawPosition(TRUE, boards[currentMove]);
3784                         DisplayBothClocks();
3785                         snprintf(str, MSG_SIZ, "%s %s %s",
3786                                 gameInfo.white, _("vs."),  gameInfo.black);
3787                         DisplayTitle(str);
3788                         gameMode = IcsIdle;
3789                     } else {
3790                         /* Moves were history of an active game */
3791                         if (gameInfo.resultDetails != NULL) {
3792                             free(gameInfo.resultDetails);
3793                             gameInfo.resultDetails = NULL;
3794                         }
3795                     }
3796                     HistorySet(parseList, backwardMostMove,
3797                                forwardMostMove, currentMove-1);
3798                     DisplayMove(currentMove - 1);
3799                     if (started == STARTED_MOVES) next_out = i;
3800                     started = STARTED_NONE;
3801                     ics_getting_history = H_FALSE;
3802                     break;
3803
3804                   case STARTED_OBSERVE:
3805                     started = STARTED_NONE;
3806                     SendToICS(ics_prefix);
3807                     SendToICS("refresh\n");
3808                     break;
3809
3810                   default:
3811                     break;
3812                 }
3813                 if(bookHit) { // [HGM] book: simulate book reply
3814                     static char bookMove[MSG_SIZ]; // a bit generous?
3815
3816                     programStats.nodes = programStats.depth = programStats.time =
3817                     programStats.score = programStats.got_only_move = 0;
3818                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3819
3820                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3821                     strcat(bookMove, bookHit);
3822                     HandleMachineMove(bookMove, &first);
3823                 }
3824                 continue;
3825             }
3826
3827             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3828                  started == STARTED_HOLDINGS ||
3829                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3830                 /* Accumulate characters in move list or board */
3831                 parse[parse_pos++] = buf[i];
3832             }
3833
3834             /* Start of game messages.  Mostly we detect start of game
3835                when the first board image arrives.  On some versions
3836                of the ICS, though, we need to do a "refresh" after starting
3837                to observe in order to get the current board right away. */
3838             if (looking_at(buf, &i, "Adding game * to observation list")) {
3839                 started = STARTED_OBSERVE;
3840                 continue;
3841             }
3842
3843             /* Handle auto-observe */
3844             if (appData.autoObserve &&
3845                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3846                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3847                 char *player;
3848                 /* Choose the player that was highlighted, if any. */
3849                 if (star_match[0][0] == '\033' ||
3850                     star_match[1][0] != '\033') {
3851                     player = star_match[0];
3852                 } else {
3853                     player = star_match[2];
3854                 }
3855                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3856                         ics_prefix, StripHighlightAndTitle(player));
3857                 SendToICS(str);
3858
3859                 /* Save ratings from notify string */
3860                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3861                 player1Rating = string_to_rating(star_match[1]);
3862                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3863                 player2Rating = string_to_rating(star_match[3]);
3864
3865                 if (appData.debugMode)
3866                   fprintf(debugFP,
3867                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3868                           player1Name, player1Rating,
3869                           player2Name, player2Rating);
3870
3871                 continue;
3872             }
3873
3874             /* Deal with automatic examine mode after a game,
3875                and with IcsObserving -> IcsExamining transition */
3876             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3877                 looking_at(buf, &i, "has made you an examiner of game *")) {
3878
3879                 int gamenum = atoi(star_match[0]);
3880                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3881                     gamenum == ics_gamenum) {
3882                     /* We were already playing or observing this game;
3883                        no need to refetch history */
3884                     gameMode = IcsExamining;
3885                     if (pausing) {
3886                         pauseExamForwardMostMove = forwardMostMove;
3887                     } else if (currentMove < forwardMostMove) {
3888                         ForwardInner(forwardMostMove);
3889                     }
3890                 } else {
3891                     /* I don't think this case really can happen */
3892                     SendToICS(ics_prefix);
3893                     SendToICS("refresh\n");
3894                 }
3895                 continue;
3896             }
3897
3898             /* Error messages */
3899 //          if (ics_user_moved) {
3900             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3901                 if (looking_at(buf, &i, "Illegal move") ||
3902                     looking_at(buf, &i, "Not a legal move") ||
3903                     looking_at(buf, &i, "Your king is in check") ||
3904                     looking_at(buf, &i, "It isn't your turn") ||
3905                     looking_at(buf, &i, "It is not your move")) {
3906                     /* Illegal move */
3907                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3908                         currentMove = forwardMostMove-1;
3909                         DisplayMove(currentMove - 1); /* before DMError */
3910                         DrawPosition(FALSE, boards[currentMove]);
3911                         SwitchClocks(forwardMostMove-1); // [HGM] race
3912                         DisplayBothClocks();
3913                     }
3914                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3915                     ics_user_moved = 0;
3916                     continue;
3917                 }
3918             }
3919
3920             if (looking_at(buf, &i, "still have time") ||
3921                 looking_at(buf, &i, "not out of time") ||
3922                 looking_at(buf, &i, "either player is out of time") ||
3923                 looking_at(buf, &i, "has timeseal; checking")) {
3924                 /* We must have called his flag a little too soon */
3925                 whiteFlag = blackFlag = FALSE;
3926                 continue;
3927             }
3928
3929             if (looking_at(buf, &i, "added * seconds to") ||
3930                 looking_at(buf, &i, "seconds were added to")) {
3931                 /* Update the clocks */
3932                 SendToICS(ics_prefix);
3933                 SendToICS("refresh\n");
3934                 continue;
3935             }
3936
3937             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3938                 ics_clock_paused = TRUE;
3939                 StopClocks();
3940                 continue;
3941             }
3942
3943             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3944                 ics_clock_paused = FALSE;
3945                 StartClocks();
3946                 continue;
3947             }
3948
3949             /* Grab player ratings from the Creating: message.
3950                Note we have to check for the special case when
3951                the ICS inserts things like [white] or [black]. */
3952             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3953                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3954                 /* star_matches:
3955                    0    player 1 name (not necessarily white)
3956                    1    player 1 rating
3957                    2    empty, white, or black (IGNORED)
3958                    3    player 2 name (not necessarily black)
3959                    4    player 2 rating
3960
3961                    The names/ratings are sorted out when the game
3962                    actually starts (below).
3963                 */
3964                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3965                 player1Rating = string_to_rating(star_match[1]);
3966                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3967                 player2Rating = string_to_rating(star_match[4]);
3968
3969                 if (appData.debugMode)
3970                   fprintf(debugFP,
3971                           "Ratings from 'Creating:' %s %d, %s %d\n",
3972                           player1Name, player1Rating,
3973                           player2Name, player2Rating);
3974
3975                 continue;
3976             }
3977
3978             /* Improved generic start/end-of-game messages */
3979             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3980                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3981                 /* If tkind == 0: */
3982                 /* star_match[0] is the game number */
3983                 /*           [1] is the white player's name */
3984                 /*           [2] is the black player's name */
3985                 /* For end-of-game: */
3986                 /*           [3] is the reason for the game end */
3987                 /*           [4] is a PGN end game-token, preceded by " " */
3988                 /* For start-of-game: */
3989                 /*           [3] begins with "Creating" or "Continuing" */
3990                 /*           [4] is " *" or empty (don't care). */
3991                 int gamenum = atoi(star_match[0]);
3992                 char *whitename, *blackname, *why, *endtoken;
3993                 ChessMove endtype = EndOfFile;
3994
3995                 if (tkind == 0) {
3996                   whitename = star_match[1];
3997                   blackname = star_match[2];
3998                   why = star_match[3];
3999                   endtoken = star_match[4];
4000                 } else {
4001                   whitename = star_match[1];
4002                   blackname = star_match[3];
4003                   why = star_match[5];
4004                   endtoken = star_match[6];
4005                 }
4006
4007                 /* Game start messages */
4008                 if (strncmp(why, "Creating ", 9) == 0 ||
4009                     strncmp(why, "Continuing ", 11) == 0) {
4010                     gs_gamenum = gamenum;
4011                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4012                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4013                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4014 #if ZIPPY
4015                     if (appData.zippyPlay) {
4016                         ZippyGameStart(whitename, blackname);
4017                     }
4018 #endif /*ZIPPY*/
4019                     partnerBoardValid = FALSE; // [HGM] bughouse
4020                     continue;
4021                 }
4022
4023                 /* Game end messages */
4024                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4025                     ics_gamenum != gamenum) {
4026                     continue;
4027                 }
4028                 while (endtoken[0] == ' ') endtoken++;
4029                 switch (endtoken[0]) {
4030                   case '*':
4031                   default:
4032                     endtype = GameUnfinished;
4033                     break;
4034                   case '0':
4035                     endtype = BlackWins;
4036                     break;
4037                   case '1':
4038                     if (endtoken[1] == '/')
4039                       endtype = GameIsDrawn;
4040                     else
4041                       endtype = WhiteWins;
4042                     break;
4043                 }
4044                 GameEnds(endtype, why, GE_ICS);
4045 #if ZIPPY
4046                 if (appData.zippyPlay && first.initDone) {
4047                     ZippyGameEnd(endtype, why);
4048                     if (first.pr == NoProc) {
4049                       /* Start the next process early so that we'll
4050                          be ready for the next challenge */
4051                       StartChessProgram(&first);
4052                     }
4053                     /* Send "new" early, in case this command takes
4054                        a long time to finish, so that we'll be ready
4055                        for the next challenge. */
4056                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4057                     Reset(TRUE, TRUE);
4058                 }
4059 #endif /*ZIPPY*/
4060                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4061                 continue;
4062             }
4063
4064             if (looking_at(buf, &i, "Removing game * from observation") ||
4065                 looking_at(buf, &i, "no longer observing game *") ||
4066                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4067                 if (gameMode == IcsObserving &&
4068                     atoi(star_match[0]) == ics_gamenum)
4069                   {
4070                       /* icsEngineAnalyze */
4071                       if (appData.icsEngineAnalyze) {
4072                             ExitAnalyzeMode();
4073                             ModeHighlight();
4074                       }
4075                       StopClocks();
4076                       gameMode = IcsIdle;
4077                       ics_gamenum = -1;
4078                       ics_user_moved = FALSE;
4079                   }
4080                 continue;
4081             }
4082
4083             if (looking_at(buf, &i, "no longer examining game *")) {
4084                 if (gameMode == IcsExamining &&
4085                     atoi(star_match[0]) == ics_gamenum)
4086                   {
4087                       gameMode = IcsIdle;
4088                       ics_gamenum = -1;
4089                       ics_user_moved = FALSE;
4090                   }
4091                 continue;
4092             }
4093
4094             /* Advance leftover_start past any newlines we find,
4095                so only partial lines can get reparsed */
4096             if (looking_at(buf, &i, "\n")) {
4097                 prevColor = curColor;
4098                 if (curColor != ColorNormal) {
4099                     if (oldi > next_out) {
4100                         SendToPlayer(&buf[next_out], oldi - next_out);
4101                         next_out = oldi;
4102                     }
4103                     Colorize(ColorNormal, FALSE);
4104                     curColor = ColorNormal;
4105                 }
4106                 if (started == STARTED_BOARD) {
4107                     started = STARTED_NONE;
4108                     parse[parse_pos] = NULLCHAR;
4109                     ParseBoard12(parse);
4110                     ics_user_moved = 0;
4111
4112                     /* Send premove here */
4113                     if (appData.premove) {
4114                       char str[MSG_SIZ];
4115                       if (currentMove == 0 &&
4116                           gameMode == IcsPlayingWhite &&
4117                           appData.premoveWhite) {
4118                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4119                         if (appData.debugMode)
4120                           fprintf(debugFP, "Sending premove:\n");
4121                         SendToICS(str);
4122                       } else if (currentMove == 1 &&
4123                                  gameMode == IcsPlayingBlack &&
4124                                  appData.premoveBlack) {
4125                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4126                         if (appData.debugMode)
4127                           fprintf(debugFP, "Sending premove:\n");
4128                         SendToICS(str);
4129                       } else if (gotPremove) {
4130                         gotPremove = 0;
4131                         ClearPremoveHighlights();
4132                         if (appData.debugMode)
4133                           fprintf(debugFP, "Sending premove:\n");
4134                           UserMoveEvent(premoveFromX, premoveFromY,
4135                                         premoveToX, premoveToY,
4136                                         premovePromoChar);
4137                       }
4138                     }
4139
4140                     /* Usually suppress following prompt */
4141                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4142                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4143                         if (looking_at(buf, &i, "*% ")) {
4144                             savingComment = FALSE;
4145                             suppressKibitz = 0;
4146                         }
4147                     }
4148                     next_out = i;
4149                 } else if (started == STARTED_HOLDINGS) {
4150                     int gamenum;
4151                     char new_piece[MSG_SIZ];
4152                     started = STARTED_NONE;
4153                     parse[parse_pos] = NULLCHAR;
4154                     if (appData.debugMode)
4155                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4156                                                         parse, currentMove);
4157                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4158                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4159                         if (gameInfo.variant == VariantNormal) {
4160                           /* [HGM] We seem to switch variant during a game!
4161                            * Presumably no holdings were displayed, so we have
4162                            * to move the position two files to the right to
4163                            * create room for them!
4164                            */
4165                           VariantClass newVariant;
4166                           switch(gameInfo.boardWidth) { // base guess on board width
4167                                 case 9:  newVariant = VariantShogi; break;
4168                                 case 10: newVariant = VariantGreat; break;
4169                                 default: newVariant = VariantCrazyhouse; break;
4170                           }
4171                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4172                           /* Get a move list just to see the header, which
4173                              will tell us whether this is really bug or zh */
4174                           if (ics_getting_history == H_FALSE) {
4175                             ics_getting_history = H_REQUESTED;
4176                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4177                             SendToICS(str);
4178                           }
4179                         }
4180                         new_piece[0] = NULLCHAR;
4181                         sscanf(parse, "game %d white [%s black [%s <- %s",
4182                                &gamenum, white_holding, black_holding,
4183                                new_piece);
4184                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4185                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4186                         /* [HGM] copy holdings to board holdings area */
4187                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4188                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4189                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4190 #if ZIPPY
4191                         if (appData.zippyPlay && first.initDone) {
4192                             ZippyHoldings(white_holding, black_holding,
4193                                           new_piece);
4194                         }
4195 #endif /*ZIPPY*/
4196                         if (tinyLayout || smallLayout) {
4197                             char wh[16], bh[16];
4198                             PackHolding(wh, white_holding);
4199                             PackHolding(bh, black_holding);
4200                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4201                                     gameInfo.white, gameInfo.black);
4202                         } else {
4203                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4204                                     gameInfo.white, white_holding, _("vs."),
4205                                     gameInfo.black, black_holding);
4206                         }
4207                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4208                         DrawPosition(FALSE, boards[currentMove]);
4209                         DisplayTitle(str);
4210                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4211                         sscanf(parse, "game %d white [%s black [%s <- %s",
4212                                &gamenum, white_holding, black_holding,
4213                                new_piece);
4214                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4215                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4216                         /* [HGM] copy holdings to partner-board holdings area */
4217                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4218                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4219                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4220                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4221                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4222                       }
4223                     }
4224                     /* Suppress following prompt */
4225                     if (looking_at(buf, &i, "*% ")) {
4226                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4227                         savingComment = FALSE;
4228                         suppressKibitz = 0;
4229                     }
4230                     next_out = i;
4231                 }
4232                 continue;
4233             }
4234
4235             i++;                /* skip unparsed character and loop back */
4236         }
4237
4238         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4239 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4240 //          SendToPlayer(&buf[next_out], i - next_out);
4241             started != STARTED_HOLDINGS && leftover_start > next_out) {
4242             SendToPlayer(&buf[next_out], leftover_start - next_out);
4243             next_out = i;
4244         }
4245
4246         leftover_len = buf_len - leftover_start;
4247         /* if buffer ends with something we couldn't parse,
4248            reparse it after appending the next read */
4249
4250     } else if (count == 0) {
4251         RemoveInputSource(isr);
4252         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4253     } else {
4254         DisplayFatalError(_("Error reading from ICS"), error, 1);
4255     }
4256 }
4257
4258
4259 /* Board style 12 looks like this:
4260
4261    <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
4262
4263  * The "<12> " is stripped before it gets to this routine.  The two
4264  * trailing 0's (flip state and clock ticking) are later addition, and
4265  * some chess servers may not have them, or may have only the first.
4266  * Additional trailing fields may be added in the future.
4267  */
4268
4269 #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"
4270
4271 #define RELATION_OBSERVING_PLAYED    0
4272 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4273 #define RELATION_PLAYING_MYMOVE      1
4274 #define RELATION_PLAYING_NOTMYMOVE  -1
4275 #define RELATION_EXAMINING           2
4276 #define RELATION_ISOLATED_BOARD     -3
4277 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4278
4279 void
4280 ParseBoard12 (char *string)
4281 {
4282 #if ZIPPY
4283     int i, takeback;
4284     char *bookHit = NULL; // [HGM] book
4285 #endif
4286     GameMode newGameMode;
4287     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4288     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4289     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4290     char to_play, board_chars[200];
4291     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4292     char black[32], white[32];
4293     Board board;
4294     int prevMove = currentMove;
4295     int ticking = 2;
4296     ChessMove moveType;
4297     int fromX, fromY, toX, toY;
4298     char promoChar;
4299     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4300     Boolean weird = FALSE, reqFlag = FALSE;
4301
4302     fromX = fromY = toX = toY = -1;
4303
4304     newGame = FALSE;
4305
4306     if (appData.debugMode)
4307       fprintf(debugFP, "Parsing board: %s\n", string);
4308
4309     move_str[0] = NULLCHAR;
4310     elapsed_time[0] = NULLCHAR;
4311     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4312         int  i = 0, j;
4313         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4314             if(string[i] == ' ') { ranks++; files = 0; }
4315             else files++;
4316             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4317             i++;
4318         }
4319         for(j = 0; j <i; j++) board_chars[j] = string[j];
4320         board_chars[i] = '\0';
4321         string += i + 1;
4322     }
4323     n = sscanf(string, PATTERN, &to_play, &double_push,
4324                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4325                &gamenum, white, black, &relation, &basetime, &increment,
4326                &white_stren, &black_stren, &white_time, &black_time,
4327                &moveNum, str, elapsed_time, move_str, &ics_flip,
4328                &ticking);
4329
4330     if (n < 21) {
4331         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4332         DisplayError(str, 0);
4333         return;
4334     }
4335
4336     /* Convert the move number to internal form */
4337     moveNum = (moveNum - 1) * 2;
4338     if (to_play == 'B') moveNum++;
4339     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4340       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4341                         0, 1);
4342       return;
4343     }
4344
4345     switch (relation) {
4346       case RELATION_OBSERVING_PLAYED:
4347       case RELATION_OBSERVING_STATIC:
4348         if (gamenum == -1) {
4349             /* Old ICC buglet */
4350             relation = RELATION_OBSERVING_STATIC;
4351         }
4352         newGameMode = IcsObserving;
4353         break;
4354       case RELATION_PLAYING_MYMOVE:
4355       case RELATION_PLAYING_NOTMYMOVE:
4356         newGameMode =
4357           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4358             IcsPlayingWhite : IcsPlayingBlack;
4359         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4360         break;
4361       case RELATION_EXAMINING:
4362         newGameMode = IcsExamining;
4363         break;
4364       case RELATION_ISOLATED_BOARD:
4365       default:
4366         /* Just display this board.  If user was doing something else,
4367            we will forget about it until the next board comes. */
4368         newGameMode = IcsIdle;
4369         break;
4370       case RELATION_STARTING_POSITION:
4371         newGameMode = gameMode;
4372         break;
4373     }
4374
4375     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4376         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4377          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4378       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4379       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4380       static int lastBgGame = -1;
4381       char *toSqr;
4382       for (k = 0; k < ranks; k++) {
4383         for (j = 0; j < files; j++)
4384           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4385         if(gameInfo.holdingsWidth > 1) {
4386              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4387              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4388         }
4389       }
4390       CopyBoard(partnerBoard, board);
4391       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4392         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4393         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4394       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4395       if(toSqr = strchr(str, '-')) {
4396         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4397         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4398       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4399       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4400       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4401       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4402       if(twoBoards) {
4403           DisplayWhiteClock(white_time*fac, to_play == 'W');
4404           DisplayBlackClock(black_time*fac, to_play != 'W');
4405           activePartner = to_play;
4406           if(gamenum != lastBgGame) {
4407               char buf[MSG_SIZ];
4408               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4409               DisplayTitle(buf);
4410           }
4411           lastBgGame = gamenum;
4412           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4413                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4414       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4415                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4416       if(!twoBoards) DisplayMessage(partnerStatus, "");
4417         partnerBoardValid = TRUE;
4418       return;
4419     }
4420
4421     if(appData.dualBoard && appData.bgObserve) {
4422         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4423             SendToICS(ics_prefix), SendToICS("pobserve\n");
4424         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4425             char buf[MSG_SIZ];
4426             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4427             SendToICS(buf);
4428         }
4429     }
4430
4431     /* Modify behavior for initial board display on move listing
4432        of wild games.
4433        */
4434     switch (ics_getting_history) {
4435       case H_FALSE:
4436       case H_REQUESTED:
4437         break;
4438       case H_GOT_REQ_HEADER:
4439       case H_GOT_UNREQ_HEADER:
4440         /* This is the initial position of the current game */
4441         gamenum = ics_gamenum;
4442         moveNum = 0;            /* old ICS bug workaround */
4443         if (to_play == 'B') {
4444           startedFromSetupPosition = TRUE;
4445           blackPlaysFirst = TRUE;
4446           moveNum = 1;
4447           if (forwardMostMove == 0) forwardMostMove = 1;
4448           if (backwardMostMove == 0) backwardMostMove = 1;
4449           if (currentMove == 0) currentMove = 1;
4450         }
4451         newGameMode = gameMode;
4452         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4453         break;
4454       case H_GOT_UNWANTED_HEADER:
4455         /* This is an initial board that we don't want */
4456         return;
4457       case H_GETTING_MOVES:
4458         /* Should not happen */
4459         DisplayError(_("Error gathering move list: extra board"), 0);
4460         ics_getting_history = H_FALSE;
4461         return;
4462     }
4463
4464    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4465                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4466                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4467      /* [HGM] We seem to have switched variant unexpectedly
4468       * Try to guess new variant from board size
4469       */
4470           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4471           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4472           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4473           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4474           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4475           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4476           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4477           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4478           /* Get a move list just to see the header, which
4479              will tell us whether this is really bug or zh */
4480           if (ics_getting_history == H_FALSE) {
4481             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4482             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4483             SendToICS(str);
4484           }
4485     }
4486
4487     /* Take action if this is the first board of a new game, or of a
4488        different game than is currently being displayed.  */
4489     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4490         relation == RELATION_ISOLATED_BOARD) {
4491
4492         /* Forget the old game and get the history (if any) of the new one */
4493         if (gameMode != BeginningOfGame) {
4494           Reset(TRUE, TRUE);
4495         }
4496         newGame = TRUE;
4497         if (appData.autoRaiseBoard) BoardToTop();
4498         prevMove = -3;
4499         if (gamenum == -1) {
4500             newGameMode = IcsIdle;
4501         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4502                    appData.getMoveList && !reqFlag) {
4503             /* Need to get game history */
4504             ics_getting_history = H_REQUESTED;
4505             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4506             SendToICS(str);
4507         }
4508
4509         /* Initially flip the board to have black on the bottom if playing
4510            black or if the ICS flip flag is set, but let the user change
4511            it with the Flip View button. */
4512         flipView = appData.autoFlipView ?
4513           (newGameMode == IcsPlayingBlack) || ics_flip :
4514           appData.flipView;
4515
4516         /* Done with values from previous mode; copy in new ones */
4517         gameMode = newGameMode;
4518         ModeHighlight();
4519         ics_gamenum = gamenum;
4520         if (gamenum == gs_gamenum) {
4521             int klen = strlen(gs_kind);
4522             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4523             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4524             gameInfo.event = StrSave(str);
4525         } else {
4526             gameInfo.event = StrSave("ICS game");
4527         }
4528         gameInfo.site = StrSave(appData.icsHost);
4529         gameInfo.date = PGNDate();
4530         gameInfo.round = StrSave("-");
4531         gameInfo.white = StrSave(white);
4532         gameInfo.black = StrSave(black);
4533         timeControl = basetime * 60 * 1000;
4534         timeControl_2 = 0;
4535         timeIncrement = increment * 1000;
4536         movesPerSession = 0;
4537         gameInfo.timeControl = TimeControlTagValue();
4538         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4539   if (appData.debugMode) {
4540     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4541     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4542     setbuf(debugFP, NULL);
4543   }
4544
4545         gameInfo.outOfBook = NULL;
4546
4547         /* Do we have the ratings? */
4548         if (strcmp(player1Name, white) == 0 &&
4549             strcmp(player2Name, black) == 0) {
4550             if (appData.debugMode)
4551               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4552                       player1Rating, player2Rating);
4553             gameInfo.whiteRating = player1Rating;
4554             gameInfo.blackRating = player2Rating;
4555         } else if (strcmp(player2Name, white) == 0 &&
4556                    strcmp(player1Name, black) == 0) {
4557             if (appData.debugMode)
4558               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4559                       player2Rating, player1Rating);
4560             gameInfo.whiteRating = player2Rating;
4561             gameInfo.blackRating = player1Rating;
4562         }
4563         player1Name[0] = player2Name[0] = NULLCHAR;
4564
4565         /* Silence shouts if requested */
4566         if (appData.quietPlay &&
4567             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4568             SendToICS(ics_prefix);
4569             SendToICS("set shout 0\n");
4570         }
4571     }
4572
4573     /* Deal with midgame name changes */
4574     if (!newGame) {
4575         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4576             if (gameInfo.white) free(gameInfo.white);
4577             gameInfo.white = StrSave(white);
4578         }
4579         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4580             if (gameInfo.black) free(gameInfo.black);
4581             gameInfo.black = StrSave(black);
4582         }
4583     }
4584
4585     /* Throw away game result if anything actually changes in examine mode */
4586     if (gameMode == IcsExamining && !newGame) {
4587         gameInfo.result = GameUnfinished;
4588         if (gameInfo.resultDetails != NULL) {
4589             free(gameInfo.resultDetails);
4590             gameInfo.resultDetails = NULL;
4591         }
4592     }
4593
4594     /* In pausing && IcsExamining mode, we ignore boards coming
4595        in if they are in a different variation than we are. */
4596     if (pauseExamInvalid) return;
4597     if (pausing && gameMode == IcsExamining) {
4598         if (moveNum <= pauseExamForwardMostMove) {
4599             pauseExamInvalid = TRUE;
4600             forwardMostMove = pauseExamForwardMostMove;
4601             return;
4602         }
4603     }
4604
4605   if (appData.debugMode) {
4606     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4607   }
4608     /* Parse the board */
4609     for (k = 0; k < ranks; k++) {
4610       for (j = 0; j < files; j++)
4611         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4612       if(gameInfo.holdingsWidth > 1) {
4613            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4614            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4615       }
4616     }
4617     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4618       board[5][BOARD_RGHT+1] = WhiteAngel;
4619       board[6][BOARD_RGHT+1] = WhiteMarshall;
4620       board[1][0] = BlackMarshall;
4621       board[2][0] = BlackAngel;
4622       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4623     }
4624     CopyBoard(boards[moveNum], board);
4625     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4626     if (moveNum == 0) {
4627         startedFromSetupPosition =
4628           !CompareBoards(board, initialPosition);
4629         if(startedFromSetupPosition)
4630             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4631     }
4632
4633     /* [HGM] Set castling rights. Take the outermost Rooks,
4634        to make it also work for FRC opening positions. Note that board12
4635        is really defective for later FRC positions, as it has no way to
4636        indicate which Rook can castle if they are on the same side of King.
4637        For the initial position we grant rights to the outermost Rooks,
4638        and remember thos rights, and we then copy them on positions
4639        later in an FRC game. This means WB might not recognize castlings with
4640        Rooks that have moved back to their original position as illegal,
4641        but in ICS mode that is not its job anyway.
4642     */
4643     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4644     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4645
4646         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4647             if(board[0][i] == WhiteRook) j = i;
4648         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4649         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4650             if(board[0][i] == WhiteRook) j = i;
4651         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4652         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4653             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4654         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4655         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4656             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4657         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4658
4659         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4660         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4661         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4662             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4663         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4664             if(board[BOARD_HEIGHT-1][k] == bKing)
4665                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4666         if(gameInfo.variant == VariantTwoKings) {
4667             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4668             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4669             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4670         }
4671     } else { int r;
4672         r = boards[moveNum][CASTLING][0] = initialRights[0];
4673         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4674         r = boards[moveNum][CASTLING][1] = initialRights[1];
4675         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4676         r = boards[moveNum][CASTLING][3] = initialRights[3];
4677         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4678         r = boards[moveNum][CASTLING][4] = initialRights[4];
4679         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4680         /* wildcastle kludge: always assume King has rights */
4681         r = boards[moveNum][CASTLING][2] = initialRights[2];
4682         r = boards[moveNum][CASTLING][5] = initialRights[5];
4683     }
4684     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4685     boards[moveNum][EP_STATUS] = EP_NONE;
4686     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4687     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4688     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4689
4690
4691     if (ics_getting_history == H_GOT_REQ_HEADER ||
4692         ics_getting_history == H_GOT_UNREQ_HEADER) {
4693         /* This was an initial position from a move list, not
4694            the current position */
4695         return;
4696     }
4697
4698     /* Update currentMove and known move number limits */
4699     newMove = newGame || moveNum > forwardMostMove;
4700
4701     if (newGame) {
4702         forwardMostMove = backwardMostMove = currentMove = moveNum;
4703         if (gameMode == IcsExamining && moveNum == 0) {
4704           /* Workaround for ICS limitation: we are not told the wild
4705              type when starting to examine a game.  But if we ask for
4706              the move list, the move list header will tell us */
4707             ics_getting_history = H_REQUESTED;
4708             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4709             SendToICS(str);
4710         }
4711     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4712                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4713 #if ZIPPY
4714         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4715         /* [HGM] applied this also to an engine that is silently watching        */
4716         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4717             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4718             gameInfo.variant == currentlyInitializedVariant) {
4719           takeback = forwardMostMove - moveNum;
4720           for (i = 0; i < takeback; i++) {
4721             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4722             SendToProgram("undo\n", &first);
4723           }
4724         }
4725 #endif
4726
4727         forwardMostMove = moveNum;
4728         if (!pausing || currentMove > forwardMostMove)
4729           currentMove = forwardMostMove;
4730     } else {
4731         /* New part of history that is not contiguous with old part */
4732         if (pausing && gameMode == IcsExamining) {
4733             pauseExamInvalid = TRUE;
4734             forwardMostMove = pauseExamForwardMostMove;
4735             return;
4736         }
4737         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4738 #if ZIPPY
4739             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4740                 // [HGM] when we will receive the move list we now request, it will be
4741                 // fed to the engine from the first move on. So if the engine is not
4742                 // in the initial position now, bring it there.
4743                 InitChessProgram(&first, 0);
4744             }
4745 #endif
4746             ics_getting_history = H_REQUESTED;
4747             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4748             SendToICS(str);
4749         }
4750         forwardMostMove = backwardMostMove = currentMove = moveNum;
4751     }
4752
4753     /* Update the clocks */
4754     if (strchr(elapsed_time, '.')) {
4755       /* Time is in ms */
4756       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4757       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4758     } else {
4759       /* Time is in seconds */
4760       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4761       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4762     }
4763
4764
4765 #if ZIPPY
4766     if (appData.zippyPlay && newGame &&
4767         gameMode != IcsObserving && gameMode != IcsIdle &&
4768         gameMode != IcsExamining)
4769       ZippyFirstBoard(moveNum, basetime, increment);
4770 #endif
4771
4772     /* Put the move on the move list, first converting
4773        to canonical algebraic form. */
4774     if (moveNum > 0) {
4775   if (appData.debugMode) {
4776     int f = forwardMostMove;
4777     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4778             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4779             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4780     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4781     fprintf(debugFP, "moveNum = %d\n", moveNum);
4782     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4783     setbuf(debugFP, NULL);
4784   }
4785         if (moveNum <= backwardMostMove) {
4786             /* We don't know what the board looked like before
4787                this move.  Punt. */
4788           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4789             strcat(parseList[moveNum - 1], " ");
4790             strcat(parseList[moveNum - 1], elapsed_time);
4791             moveList[moveNum - 1][0] = NULLCHAR;
4792         } else if (strcmp(move_str, "none") == 0) {
4793             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4794             /* Again, we don't know what the board looked like;
4795                this is really the start of the game. */
4796             parseList[moveNum - 1][0] = NULLCHAR;
4797             moveList[moveNum - 1][0] = NULLCHAR;
4798             backwardMostMove = moveNum;
4799             startedFromSetupPosition = TRUE;
4800             fromX = fromY = toX = toY = -1;
4801         } else {
4802           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4803           //                 So we parse the long-algebraic move string in stead of the SAN move
4804           int valid; char buf[MSG_SIZ], *prom;
4805
4806           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4807                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4808           // str looks something like "Q/a1-a2"; kill the slash
4809           if(str[1] == '/')
4810             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4811           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4812           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4813                 strcat(buf, prom); // long move lacks promo specification!
4814           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4815                 if(appData.debugMode)
4816                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4817                 safeStrCpy(move_str, buf, MSG_SIZ);
4818           }
4819           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4820                                 &fromX, &fromY, &toX, &toY, &promoChar)
4821                || ParseOneMove(buf, moveNum - 1, &moveType,
4822                                 &fromX, &fromY, &toX, &toY, &promoChar);
4823           // end of long SAN patch
4824           if (valid) {
4825             (void) CoordsToAlgebraic(boards[moveNum - 1],
4826                                      PosFlags(moveNum - 1),
4827                                      fromY, fromX, toY, toX, promoChar,
4828                                      parseList[moveNum-1]);
4829             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4830               case MT_NONE:
4831               case MT_STALEMATE:
4832               default:
4833                 break;
4834               case MT_CHECK:
4835                 if(!IS_SHOGI(gameInfo.variant))
4836                     strcat(parseList[moveNum - 1], "+");
4837                 break;
4838               case MT_CHECKMATE:
4839               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4840                 strcat(parseList[moveNum - 1], "#");
4841                 break;
4842             }
4843             strcat(parseList[moveNum - 1], " ");
4844             strcat(parseList[moveNum - 1], elapsed_time);
4845             /* currentMoveString is set as a side-effect of ParseOneMove */
4846             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4847             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4848             strcat(moveList[moveNum - 1], "\n");
4849
4850             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4851                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4852               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4853                 ChessSquare old, new = boards[moveNum][k][j];
4854                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4855                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4856                   if(old == new) continue;
4857                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4858                   else if(new == WhiteWazir || new == BlackWazir) {
4859                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4860                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4861                       else boards[moveNum][k][j] = old; // preserve type of Gold
4862                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4863                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4864               }
4865           } else {
4866             /* Move from ICS was illegal!?  Punt. */
4867             if (appData.debugMode) {
4868               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4869               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4870             }
4871             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4872             strcat(parseList[moveNum - 1], " ");
4873             strcat(parseList[moveNum - 1], elapsed_time);
4874             moveList[moveNum - 1][0] = NULLCHAR;
4875             fromX = fromY = toX = toY = -1;
4876           }
4877         }
4878   if (appData.debugMode) {
4879     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4880     setbuf(debugFP, NULL);
4881   }
4882
4883 #if ZIPPY
4884         /* Send move to chess program (BEFORE animating it). */
4885         if (appData.zippyPlay && !newGame && newMove &&
4886            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4887
4888             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4889                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4890                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4891                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4892                             move_str);
4893                     DisplayError(str, 0);
4894                 } else {
4895                     if (first.sendTime) {
4896                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4897                     }
4898                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4899                     if (firstMove && !bookHit) {
4900                         firstMove = FALSE;
4901                         if (first.useColors) {
4902                           SendToProgram(gameMode == IcsPlayingWhite ?
4903                                         "white\ngo\n" :
4904                                         "black\ngo\n", &first);
4905                         } else {
4906                           SendToProgram("go\n", &first);
4907                         }
4908                         first.maybeThinking = TRUE;
4909                     }
4910                 }
4911             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4912               if (moveList[moveNum - 1][0] == NULLCHAR) {
4913                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4914                 DisplayError(str, 0);
4915               } else {
4916                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4917                 SendMoveToProgram(moveNum - 1, &first);
4918               }
4919             }
4920         }
4921 #endif
4922     }
4923
4924     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4925         /* If move comes from a remote source, animate it.  If it
4926            isn't remote, it will have already been animated. */
4927         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4928             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4929         }
4930         if (!pausing && appData.highlightLastMove) {
4931             SetHighlights(fromX, fromY, toX, toY);
4932         }
4933     }
4934
4935     /* Start the clocks */
4936     whiteFlag = blackFlag = FALSE;
4937     appData.clockMode = !(basetime == 0 && increment == 0);
4938     if (ticking == 0) {
4939       ics_clock_paused = TRUE;
4940       StopClocks();
4941     } else if (ticking == 1) {
4942       ics_clock_paused = FALSE;
4943     }
4944     if (gameMode == IcsIdle ||
4945         relation == RELATION_OBSERVING_STATIC ||
4946         relation == RELATION_EXAMINING ||
4947         ics_clock_paused)
4948       DisplayBothClocks();
4949     else
4950       StartClocks();
4951
4952     /* Display opponents and material strengths */
4953     if (gameInfo.variant != VariantBughouse &&
4954         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4955         if (tinyLayout || smallLayout) {
4956             if(gameInfo.variant == VariantNormal)
4957               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4958                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4959                     basetime, increment);
4960             else
4961               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4962                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4963                     basetime, increment, (int) gameInfo.variant);
4964         } else {
4965             if(gameInfo.variant == VariantNormal)
4966               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4967                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4968                     basetime, increment);
4969             else
4970               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4971                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4972                     basetime, increment, VariantName(gameInfo.variant));
4973         }
4974         DisplayTitle(str);
4975   if (appData.debugMode) {
4976     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4977   }
4978     }
4979
4980
4981     /* Display the board */
4982     if (!pausing && !appData.noGUI) {
4983
4984       if (appData.premove)
4985           if (!gotPremove ||
4986              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4987              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4988               ClearPremoveHighlights();
4989
4990       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4991         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4992       DrawPosition(j, boards[currentMove]);
4993
4994       DisplayMove(moveNum - 1);
4995       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4996             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4997               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4998         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4999       }
5000     }
5001
5002     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5003 #if ZIPPY
5004     if(bookHit) { // [HGM] book: simulate book reply
5005         static char bookMove[MSG_SIZ]; // a bit generous?
5006
5007         programStats.nodes = programStats.depth = programStats.time =
5008         programStats.score = programStats.got_only_move = 0;
5009         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5010
5011         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5012         strcat(bookMove, bookHit);
5013         HandleMachineMove(bookMove, &first);
5014     }
5015 #endif
5016 }
5017
5018 void
5019 GetMoveListEvent ()
5020 {
5021     char buf[MSG_SIZ];
5022     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5023         ics_getting_history = H_REQUESTED;
5024         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5025         SendToICS(buf);
5026     }
5027 }
5028
5029 void
5030 SendToBoth (char *msg)
5031 {   // to make it easy to keep two engines in step in dual analysis
5032     SendToProgram(msg, &first);
5033     if(second.analyzing) SendToProgram(msg, &second);
5034 }
5035
5036 void
5037 AnalysisPeriodicEvent (int force)
5038 {
5039     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5040          && !force) || !appData.periodicUpdates)
5041       return;
5042
5043     /* Send . command to Crafty to collect stats */
5044     SendToBoth(".\n");
5045
5046     /* Don't send another until we get a response (this makes
5047        us stop sending to old Crafty's which don't understand
5048        the "." command (sending illegal cmds resets node count & time,
5049        which looks bad)) */
5050     programStats.ok_to_send = 0;
5051 }
5052
5053 void
5054 ics_update_width (int new_width)
5055 {
5056         ics_printf("set width %d\n", new_width);
5057 }
5058
5059 void
5060 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5061 {
5062     char buf[MSG_SIZ];
5063
5064     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5065         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5066             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5067             SendToProgram(buf, cps);
5068             return;
5069         }
5070         // null move in variant where engine does not understand it (for analysis purposes)
5071         SendBoard(cps, moveNum + 1); // send position after move in stead.
5072         return;
5073     }
5074     if (cps->useUsermove) {
5075       SendToProgram("usermove ", cps);
5076     }
5077     if (cps->useSAN) {
5078       char *space;
5079       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5080         int len = space - parseList[moveNum];
5081         memcpy(buf, parseList[moveNum], len);
5082         buf[len++] = '\n';
5083         buf[len] = NULLCHAR;
5084       } else {
5085         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5086       }
5087       SendToProgram(buf, cps);
5088     } else {
5089       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5090         AlphaRank(moveList[moveNum], 4);
5091         SendToProgram(moveList[moveNum], cps);
5092         AlphaRank(moveList[moveNum], 4); // and back
5093       } else
5094       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5095        * the engine. It would be nice to have a better way to identify castle
5096        * moves here. */
5097       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
5098                                                                          && cps->useOOCastle) {
5099         int fromX = moveList[moveNum][0] - AAA;
5100         int fromY = moveList[moveNum][1] - ONE;
5101         int toX = moveList[moveNum][2] - AAA;
5102         int toY = moveList[moveNum][3] - ONE;
5103         if((boards[moveNum][fromY][fromX] == WhiteKing
5104             && boards[moveNum][toY][toX] == WhiteRook)
5105            || (boards[moveNum][fromY][fromX] == BlackKing
5106                && boards[moveNum][toY][toX] == BlackRook)) {
5107           if(toX > fromX) SendToProgram("O-O\n", cps);
5108           else SendToProgram("O-O-O\n", cps);
5109         }
5110         else SendToProgram(moveList[moveNum], cps);
5111       } else
5112       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5113           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5114                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5115                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5116                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5117           SendToProgram(buf, cps);
5118       } else
5119       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5120         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5121           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5122           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5123                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5124         } else
5125           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5126                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5127         SendToProgram(buf, cps);
5128       }
5129       else SendToProgram(moveList[moveNum], cps);
5130       /* End of additions by Tord */
5131     }
5132
5133     /* [HGM] setting up the opening has brought engine in force mode! */
5134     /*       Send 'go' if we are in a mode where machine should play. */
5135     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5136         (gameMode == TwoMachinesPlay   ||
5137 #if ZIPPY
5138          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5139 #endif
5140          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5141         SendToProgram("go\n", cps);
5142   if (appData.debugMode) {
5143     fprintf(debugFP, "(extra)\n");
5144   }
5145     }
5146     setboardSpoiledMachineBlack = 0;
5147 }
5148
5149 void
5150 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5151 {
5152     char user_move[MSG_SIZ];
5153     char suffix[4];
5154
5155     if(gameInfo.variant == VariantSChess && promoChar) {
5156         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5157         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5158     } else suffix[0] = NULLCHAR;
5159
5160     switch (moveType) {
5161       default:
5162         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5163                 (int)moveType, fromX, fromY, toX, toY);
5164         DisplayError(user_move + strlen("say "), 0);
5165         break;
5166       case WhiteKingSideCastle:
5167       case BlackKingSideCastle:
5168       case WhiteQueenSideCastleWild:
5169       case BlackQueenSideCastleWild:
5170       /* PUSH Fabien */
5171       case WhiteHSideCastleFR:
5172       case BlackHSideCastleFR:
5173       /* POP Fabien */
5174         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5175         break;
5176       case WhiteQueenSideCastle:
5177       case BlackQueenSideCastle:
5178       case WhiteKingSideCastleWild:
5179       case BlackKingSideCastleWild:
5180       /* PUSH Fabien */
5181       case WhiteASideCastleFR:
5182       case BlackASideCastleFR:
5183       /* POP Fabien */
5184         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5185         break;
5186       case WhiteNonPromotion:
5187       case BlackNonPromotion:
5188         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5189         break;
5190       case WhitePromotion:
5191       case BlackPromotion:
5192         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5193            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5194           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5195                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5196                 PieceToChar(WhiteFerz));
5197         else if(gameInfo.variant == VariantGreat)
5198           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5199                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5200                 PieceToChar(WhiteMan));
5201         else
5202           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5203                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5204                 promoChar);
5205         break;
5206       case WhiteDrop:
5207       case BlackDrop:
5208       drop:
5209         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5210                  ToUpper(PieceToChar((ChessSquare) fromX)),
5211                  AAA + toX, ONE + toY);
5212         break;
5213       case IllegalMove:  /* could be a variant we don't quite understand */
5214         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5215       case NormalMove:
5216       case WhiteCapturesEnPassant:
5217       case BlackCapturesEnPassant:
5218         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5219                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5220         break;
5221     }
5222     SendToICS(user_move);
5223     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5224         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5225 }
5226
5227 void
5228 UploadGameEvent ()
5229 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5230     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5231     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5232     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5233       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5234       return;
5235     }
5236     if(gameMode != IcsExamining) { // is this ever not the case?
5237         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5238
5239         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5240           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5241         } else { // on FICS we must first go to general examine mode
5242           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5243         }
5244         if(gameInfo.variant != VariantNormal) {
5245             // try figure out wild number, as xboard names are not always valid on ICS
5246             for(i=1; i<=36; i++) {
5247               snprintf(buf, MSG_SIZ, "wild/%d", i);
5248                 if(StringToVariant(buf) == gameInfo.variant) break;
5249             }
5250             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5251             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5252             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5253         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5254         SendToICS(ics_prefix);
5255         SendToICS(buf);
5256         if(startedFromSetupPosition || backwardMostMove != 0) {
5257           fen = PositionToFEN(backwardMostMove, NULL, 1);
5258           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5259             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5260             SendToICS(buf);
5261           } else { // FICS: everything has to set by separate bsetup commands
5262             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5263             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5264             SendToICS(buf);
5265             if(!WhiteOnMove(backwardMostMove)) {
5266                 SendToICS("bsetup tomove black\n");
5267             }
5268             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5269             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5270             SendToICS(buf);
5271             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5272             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5273             SendToICS(buf);
5274             i = boards[backwardMostMove][EP_STATUS];
5275             if(i >= 0) { // set e.p.
5276               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5277                 SendToICS(buf);
5278             }
5279             bsetup++;
5280           }
5281         }
5282       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5283             SendToICS("bsetup done\n"); // switch to normal examining.
5284     }
5285     for(i = backwardMostMove; i<last; i++) {
5286         char buf[20];
5287         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5288         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5289             int len = strlen(moveList[i]);
5290             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5291             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5292         }
5293         SendToICS(buf);
5294     }
5295     SendToICS(ics_prefix);
5296     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5297 }
5298
5299 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5300
5301 void
5302 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5303 {
5304     if (rf == DROP_RANK) {
5305       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5306       sprintf(move, "%c@%c%c\n",
5307                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5308     } else {
5309         if (promoChar == 'x' || promoChar == NULLCHAR) {
5310           sprintf(move, "%c%c%c%c\n",
5311                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5312           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5313         } else {
5314             sprintf(move, "%c%c%c%c%c\n",
5315                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5316         }
5317     }
5318 }
5319
5320 void
5321 ProcessICSInitScript (FILE *f)
5322 {
5323     char buf[MSG_SIZ];
5324
5325     while (fgets(buf, MSG_SIZ, f)) {
5326         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5327     }
5328
5329     fclose(f);
5330 }
5331
5332
5333 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5334 int dragging;
5335 static ClickType lastClickType;
5336
5337 int
5338 Partner (ChessSquare *p)
5339 { // change piece into promotion partner if one shogi-promotes to the other
5340   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5341   ChessSquare partner;
5342   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5343   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5344   *p = partner;
5345   return 1;
5346 }
5347
5348 void
5349 Sweep (int step)
5350 {
5351     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5352     static int toggleFlag;
5353     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5354     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5355     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5356     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5357     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5358     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5359     do {
5360         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5361         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5362         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5363         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5364         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5365         if(!step) step = -1;
5366     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5367             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5368             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5369             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5370     if(toX >= 0) {
5371         int victim = boards[currentMove][toY][toX];
5372         boards[currentMove][toY][toX] = promoSweep;
5373         DrawPosition(FALSE, boards[currentMove]);
5374         boards[currentMove][toY][toX] = victim;
5375     } else
5376     ChangeDragPiece(promoSweep);
5377 }
5378
5379 int
5380 PromoScroll (int x, int y)
5381 {
5382   int step = 0;
5383
5384   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5385   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5386   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5387   if(!step) return FALSE;
5388   lastX = x; lastY = y;
5389   if((promoSweep < BlackPawn) == flipView) step = -step;
5390   if(step > 0) selectFlag = 1;
5391   if(!selectFlag) Sweep(step);
5392   return FALSE;
5393 }
5394
5395 void
5396 NextPiece (int step)
5397 {
5398     ChessSquare piece = boards[currentMove][toY][toX];
5399     do {
5400         pieceSweep -= step;
5401         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5402         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5403         if(!step) step = -1;
5404     } while(PieceToChar(pieceSweep) == '.');
5405     boards[currentMove][toY][toX] = pieceSweep;
5406     DrawPosition(FALSE, boards[currentMove]);
5407     boards[currentMove][toY][toX] = piece;
5408 }
5409 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5410 void
5411 AlphaRank (char *move, int n)
5412 {
5413 //    char *p = move, c; int x, y;
5414
5415     if (appData.debugMode) {
5416         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5417     }
5418
5419     if(move[1]=='*' &&
5420        move[2]>='0' && move[2]<='9' &&
5421        move[3]>='a' && move[3]<='x'    ) {
5422         move[1] = '@';
5423         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5424         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5425     } else
5426     if(move[0]>='0' && move[0]<='9' &&
5427        move[1]>='a' && move[1]<='x' &&
5428        move[2]>='0' && move[2]<='9' &&
5429        move[3]>='a' && move[3]<='x'    ) {
5430         /* input move, Shogi -> normal */
5431         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5432         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5433         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5434         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5435     } else
5436     if(move[1]=='@' &&
5437        move[3]>='0' && move[3]<='9' &&
5438        move[2]>='a' && move[2]<='x'    ) {
5439         move[1] = '*';
5440         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5441         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5442     } else
5443     if(
5444        move[0]>='a' && move[0]<='x' &&
5445        move[3]>='0' && move[3]<='9' &&
5446        move[2]>='a' && move[2]<='x'    ) {
5447          /* output move, normal -> Shogi */
5448         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5449         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5450         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5451         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5452         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5453     }
5454     if (appData.debugMode) {
5455         fprintf(debugFP, "   out = '%s'\n", move);
5456     }
5457 }
5458
5459 char yy_textstr[8000];
5460
5461 /* Parser for moves from gnuchess, ICS, or user typein box */
5462 Boolean
5463 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5464 {
5465     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5466
5467     switch (*moveType) {
5468       case WhitePromotion:
5469       case BlackPromotion:
5470       case WhiteNonPromotion:
5471       case BlackNonPromotion:
5472       case NormalMove:
5473       case FirstLeg:
5474       case WhiteCapturesEnPassant:
5475       case BlackCapturesEnPassant:
5476       case WhiteKingSideCastle:
5477       case WhiteQueenSideCastle:
5478       case BlackKingSideCastle:
5479       case BlackQueenSideCastle:
5480       case WhiteKingSideCastleWild:
5481       case WhiteQueenSideCastleWild:
5482       case BlackKingSideCastleWild:
5483       case BlackQueenSideCastleWild:
5484       /* Code added by Tord: */
5485       case WhiteHSideCastleFR:
5486       case WhiteASideCastleFR:
5487       case BlackHSideCastleFR:
5488       case BlackASideCastleFR:
5489       /* End of code added by Tord */
5490       case IllegalMove:         /* bug or odd chess variant */
5491         *fromX = currentMoveString[0] - AAA;
5492         *fromY = currentMoveString[1] - ONE;
5493         *toX = currentMoveString[2] - AAA;
5494         *toY = currentMoveString[3] - ONE;
5495         *promoChar = currentMoveString[4];
5496         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5497             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5498     if (appData.debugMode) {
5499         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5500     }
5501             *fromX = *fromY = *toX = *toY = 0;
5502             return FALSE;
5503         }
5504         if (appData.testLegality) {
5505           return (*moveType != IllegalMove);
5506         } else {
5507           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5508                          // [HGM] lion: if this is a double move we are less critical
5509                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5510         }
5511
5512       case WhiteDrop:
5513       case BlackDrop:
5514         *fromX = *moveType == WhiteDrop ?
5515           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5516           (int) CharToPiece(ToLower(currentMoveString[0]));
5517         *fromY = DROP_RANK;
5518         *toX = currentMoveString[2] - AAA;
5519         *toY = currentMoveString[3] - ONE;
5520         *promoChar = NULLCHAR;
5521         return TRUE;
5522
5523       case AmbiguousMove:
5524       case ImpossibleMove:
5525       case EndOfFile:
5526       case ElapsedTime:
5527       case Comment:
5528       case PGNTag:
5529       case NAG:
5530       case WhiteWins:
5531       case BlackWins:
5532       case GameIsDrawn:
5533       default:
5534     if (appData.debugMode) {
5535         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5536     }
5537         /* bug? */
5538         *fromX = *fromY = *toX = *toY = 0;
5539         *promoChar = NULLCHAR;
5540         return FALSE;
5541     }
5542 }
5543
5544 Boolean pushed = FALSE;
5545 char *lastParseAttempt;
5546
5547 void
5548 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5549 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5550   int fromX, fromY, toX, toY; char promoChar;
5551   ChessMove moveType;
5552   Boolean valid;
5553   int nr = 0;
5554
5555   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5556   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5557     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5558     pushed = TRUE;
5559   }
5560   endPV = forwardMostMove;
5561   do {
5562     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5563     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5564     lastParseAttempt = pv;
5565     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5566     if(!valid && nr == 0 &&
5567        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5568         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5569         // Hande case where played move is different from leading PV move
5570         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5571         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5572         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5573         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5574           endPV += 2; // if position different, keep this
5575           moveList[endPV-1][0] = fromX + AAA;
5576           moveList[endPV-1][1] = fromY + ONE;
5577           moveList[endPV-1][2] = toX + AAA;
5578           moveList[endPV-1][3] = toY + ONE;
5579           parseList[endPV-1][0] = NULLCHAR;
5580           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5581         }
5582       }
5583     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5584     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5585     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5586     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5587         valid++; // allow comments in PV
5588         continue;
5589     }
5590     nr++;
5591     if(endPV+1 > framePtr) break; // no space, truncate
5592     if(!valid) break;
5593     endPV++;
5594     CopyBoard(boards[endPV], boards[endPV-1]);
5595     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5596     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5597     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5598     CoordsToAlgebraic(boards[endPV - 1],
5599                              PosFlags(endPV - 1),
5600                              fromY, fromX, toY, toX, promoChar,
5601                              parseList[endPV - 1]);
5602   } while(valid);
5603   if(atEnd == 2) return; // used hidden, for PV conversion
5604   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5605   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5606   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5607                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5608   DrawPosition(TRUE, boards[currentMove]);
5609 }
5610
5611 int
5612 MultiPV (ChessProgramState *cps)
5613 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5614         int i;
5615         for(i=0; i<cps->nrOptions; i++)
5616             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5617                 return i;
5618         return -1;
5619 }
5620
5621 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5622
5623 Boolean
5624 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5625 {
5626         int startPV, multi, lineStart, origIndex = index;
5627         char *p, buf2[MSG_SIZ];
5628         ChessProgramState *cps = (pane ? &second : &first);
5629
5630         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5631         lastX = x; lastY = y;
5632         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5633         lineStart = startPV = index;
5634         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5635         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5636         index = startPV;
5637         do{ while(buf[index] && buf[index] != '\n') index++;
5638         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5639         buf[index] = 0;
5640         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5641                 int n = cps->option[multi].value;
5642                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5643                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5644                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5645                 cps->option[multi].value = n;
5646                 *start = *end = 0;
5647                 return FALSE;
5648         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5649                 ExcludeClick(origIndex - lineStart);
5650                 return FALSE;
5651         }
5652         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5653         *start = startPV; *end = index-1;
5654         extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
5655         return TRUE;
5656 }
5657
5658 char *
5659 PvToSAN (char *pv)
5660 {
5661         static char buf[10*MSG_SIZ];
5662         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5663         *buf = NULLCHAR;
5664         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5665         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5666         for(i = forwardMostMove; i<endPV; i++){
5667             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5668             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5669             k += strlen(buf+k);
5670         }
5671         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5672         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5673         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5674         endPV = savedEnd;
5675         return buf;
5676 }
5677
5678 Boolean
5679 LoadPV (int x, int y)
5680 { // called on right mouse click to load PV
5681   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5682   lastX = x; lastY = y;
5683   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5684   extendGame = FALSE;
5685   return TRUE;
5686 }
5687
5688 void
5689 UnLoadPV ()
5690 {
5691   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5692   if(endPV < 0) return;
5693   if(appData.autoCopyPV) CopyFENToClipboard();
5694   endPV = -1;
5695   if(extendGame && currentMove > forwardMostMove) {
5696         Boolean saveAnimate = appData.animate;
5697         if(pushed) {
5698             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5699                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5700             } else storedGames--; // abandon shelved tail of original game
5701         }
5702         pushed = FALSE;
5703         forwardMostMove = currentMove;
5704         currentMove = oldFMM;
5705         appData.animate = FALSE;
5706         ToNrEvent(forwardMostMove);
5707         appData.animate = saveAnimate;
5708   }
5709   currentMove = forwardMostMove;
5710   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5711   ClearPremoveHighlights();
5712   DrawPosition(TRUE, boards[currentMove]);
5713 }
5714
5715 void
5716 MovePV (int x, int y, int h)
5717 { // step through PV based on mouse coordinates (called on mouse move)
5718   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5719
5720   // we must somehow check if right button is still down (might be released off board!)
5721   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5722   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5723   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5724   if(!step) return;
5725   lastX = x; lastY = y;
5726
5727   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5728   if(endPV < 0) return;
5729   if(y < margin) step = 1; else
5730   if(y > h - margin) step = -1;
5731   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5732   currentMove += step;
5733   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5734   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5735                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5736   DrawPosition(FALSE, boards[currentMove]);
5737 }
5738
5739
5740 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5741 // All positions will have equal probability, but the current method will not provide a unique
5742 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5743 #define DARK 1
5744 #define LITE 2
5745 #define ANY 3
5746
5747 int squaresLeft[4];
5748 int piecesLeft[(int)BlackPawn];
5749 int seed, nrOfShuffles;
5750
5751 void
5752 GetPositionNumber ()
5753 {       // sets global variable seed
5754         int i;
5755
5756         seed = appData.defaultFrcPosition;
5757         if(seed < 0) { // randomize based on time for negative FRC position numbers
5758                 for(i=0; i<50; i++) seed += random();
5759                 seed = random() ^ random() >> 8 ^ random() << 8;
5760                 if(seed<0) seed = -seed;
5761         }
5762 }
5763
5764 int
5765 put (Board board, int pieceType, int rank, int n, int shade)
5766 // put the piece on the (n-1)-th empty squares of the given shade
5767 {
5768         int i;
5769
5770         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5771                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5772                         board[rank][i] = (ChessSquare) pieceType;
5773                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5774                         squaresLeft[ANY]--;
5775                         piecesLeft[pieceType]--;
5776                         return i;
5777                 }
5778         }
5779         return -1;
5780 }
5781
5782
5783 void
5784 AddOnePiece (Board board, int pieceType, int rank, int shade)
5785 // calculate where the next piece goes, (any empty square), and put it there
5786 {
5787         int i;
5788
5789         i = seed % squaresLeft[shade];
5790         nrOfShuffles *= squaresLeft[shade];
5791         seed /= squaresLeft[shade];
5792         put(board, pieceType, rank, i, shade);
5793 }
5794
5795 void
5796 AddTwoPieces (Board board, int pieceType, int rank)
5797 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5798 {
5799         int i, n=squaresLeft[ANY], j=n-1, k;
5800
5801         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5802         i = seed % k;  // pick one
5803         nrOfShuffles *= k;
5804         seed /= k;
5805         while(i >= j) i -= j--;
5806         j = n - 1 - j; i += j;
5807         put(board, pieceType, rank, j, ANY);
5808         put(board, pieceType, rank, i, ANY);
5809 }
5810
5811 void
5812 SetUpShuffle (Board board, int number)
5813 {
5814         int i, p, first=1;
5815
5816         GetPositionNumber(); nrOfShuffles = 1;
5817
5818         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5819         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5820         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5821
5822         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5823
5824         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5825             p = (int) board[0][i];
5826             if(p < (int) BlackPawn) piecesLeft[p] ++;
5827             board[0][i] = EmptySquare;
5828         }
5829
5830         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5831             // shuffles restricted to allow normal castling put KRR first
5832             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5833                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5834             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5835                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5836             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5837                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5838             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5839                 put(board, WhiteRook, 0, 0, ANY);
5840             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5841         }
5842
5843         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5844             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5845             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5846                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5847                 while(piecesLeft[p] >= 2) {
5848                     AddOnePiece(board, p, 0, LITE);
5849                     AddOnePiece(board, p, 0, DARK);
5850                 }
5851                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5852             }
5853
5854         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5855             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5856             // but we leave King and Rooks for last, to possibly obey FRC restriction
5857             if(p == (int)WhiteRook) continue;
5858             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5859             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5860         }
5861
5862         // now everything is placed, except perhaps King (Unicorn) and Rooks
5863
5864         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5865             // Last King gets castling rights
5866             while(piecesLeft[(int)WhiteUnicorn]) {
5867                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5868                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5869             }
5870
5871             while(piecesLeft[(int)WhiteKing]) {
5872                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5873                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5874             }
5875
5876
5877         } else {
5878             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5879             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5880         }
5881
5882         // Only Rooks can be left; simply place them all
5883         while(piecesLeft[(int)WhiteRook]) {
5884                 i = put(board, WhiteRook, 0, 0, ANY);
5885                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5886                         if(first) {
5887                                 first=0;
5888                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5889                         }
5890                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5891                 }
5892         }
5893         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5894             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5895         }
5896
5897         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5898 }
5899
5900 int
5901 SetCharTable (char *table, const char * map)
5902 /* [HGM] moved here from winboard.c because of its general usefulness */
5903 /*       Basically a safe strcpy that uses the last character as King */
5904 {
5905     int result = FALSE; int NrPieces;
5906
5907     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5908                     && NrPieces >= 12 && !(NrPieces&1)) {
5909         int i; /* [HGM] Accept even length from 12 to 34 */
5910
5911         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5912         for( i=0; i<NrPieces/2-1; i++ ) {
5913             table[i] = map[i];
5914             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5915         }
5916         table[(int) WhiteKing]  = map[NrPieces/2-1];
5917         table[(int) BlackKing]  = map[NrPieces-1];
5918
5919         result = TRUE;
5920     }
5921
5922     return result;
5923 }
5924
5925 void
5926 Prelude (Board board)
5927 {       // [HGM] superchess: random selection of exo-pieces
5928         int i, j, k; ChessSquare p;
5929         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5930
5931         GetPositionNumber(); // use FRC position number
5932
5933         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5934             SetCharTable(pieceToChar, appData.pieceToCharTable);
5935             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5936                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5937         }
5938
5939         j = seed%4;                 seed /= 4;
5940         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5941         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5942         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5943         j = seed%3 + (seed%3 >= j); seed /= 3;
5944         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5945         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5946         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5947         j = seed%3;                 seed /= 3;
5948         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5949         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5950         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5951         j = seed%2 + (seed%2 >= j); seed /= 2;
5952         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5953         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5954         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5955         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5956         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5957         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5958         put(board, exoPieces[0],    0, 0, ANY);
5959         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5960 }
5961
5962 void
5963 InitPosition (int redraw)
5964 {
5965     ChessSquare (* pieces)[BOARD_FILES];
5966     int i, j, pawnRow=1, pieceRows=1, overrule,
5967     oldx = gameInfo.boardWidth,
5968     oldy = gameInfo.boardHeight,
5969     oldh = gameInfo.holdingsWidth;
5970     static int oldv;
5971
5972     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5973
5974     /* [AS] Initialize pv info list [HGM] and game status */
5975     {
5976         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5977             pvInfoList[i].depth = 0;
5978             boards[i][EP_STATUS] = EP_NONE;
5979             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5980         }
5981
5982         initialRulePlies = 0; /* 50-move counter start */
5983
5984         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5985         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5986     }
5987
5988
5989     /* [HGM] logic here is completely changed. In stead of full positions */
5990     /* the initialized data only consist of the two backranks. The switch */
5991     /* selects which one we will use, which is than copied to the Board   */
5992     /* initialPosition, which for the rest is initialized by Pawns and    */
5993     /* empty squares. This initial position is then copied to boards[0],  */
5994     /* possibly after shuffling, so that it remains available.            */
5995
5996     gameInfo.holdingsWidth = 0; /* default board sizes */
5997     gameInfo.boardWidth    = 8;
5998     gameInfo.boardHeight   = 8;
5999     gameInfo.holdingsSize  = 0;
6000     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6001     for(i=0; i<BOARD_FILES-2; i++)
6002       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6003     initialPosition[EP_STATUS] = EP_NONE;
6004     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6005     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6006          SetCharTable(pieceNickName, appData.pieceNickNames);
6007     else SetCharTable(pieceNickName, "............");
6008     pieces = FIDEArray;
6009
6010     switch (gameInfo.variant) {
6011     case VariantFischeRandom:
6012       shuffleOpenings = TRUE;
6013     default:
6014       break;
6015     case VariantShatranj:
6016       pieces = ShatranjArray;
6017       nrCastlingRights = 0;
6018       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6019       break;
6020     case VariantMakruk:
6021       pieces = makrukArray;
6022       nrCastlingRights = 0;
6023       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6024       break;
6025     case VariantASEAN:
6026       pieces = aseanArray;
6027       nrCastlingRights = 0;
6028       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6029       break;
6030     case VariantTwoKings:
6031       pieces = twoKingsArray;
6032       break;
6033     case VariantGrand:
6034       pieces = GrandArray;
6035       nrCastlingRights = 0;
6036       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6037       gameInfo.boardWidth = 10;
6038       gameInfo.boardHeight = 10;
6039       gameInfo.holdingsSize = 7;
6040       break;
6041     case VariantCapaRandom:
6042       shuffleOpenings = TRUE;
6043     case VariantCapablanca:
6044       pieces = CapablancaArray;
6045       gameInfo.boardWidth = 10;
6046       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6047       break;
6048     case VariantGothic:
6049       pieces = GothicArray;
6050       gameInfo.boardWidth = 10;
6051       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6052       break;
6053     case VariantSChess:
6054       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6055       gameInfo.holdingsSize = 7;
6056       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6057       break;
6058     case VariantJanus:
6059       pieces = JanusArray;
6060       gameInfo.boardWidth = 10;
6061       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6062       nrCastlingRights = 6;
6063         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6064         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6065         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6066         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6067         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6068         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6069       break;
6070     case VariantFalcon:
6071       pieces = FalconArray;
6072       gameInfo.boardWidth = 10;
6073       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
6074       break;
6075     case VariantXiangqi:
6076       pieces = XiangqiArray;
6077       gameInfo.boardWidth  = 9;
6078       gameInfo.boardHeight = 10;
6079       nrCastlingRights = 0;
6080       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6081       break;
6082     case VariantShogi:
6083       pieces = ShogiArray;
6084       gameInfo.boardWidth  = 9;
6085       gameInfo.boardHeight = 9;
6086       gameInfo.holdingsSize = 7;
6087       nrCastlingRights = 0;
6088       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6089       break;
6090     case VariantChu:
6091       pieces = ChuArray; pieceRows = 3;
6092       gameInfo.boardWidth  = 12;
6093       gameInfo.boardHeight = 12;
6094       nrCastlingRights = 0;
6095       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6096                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6097       break;
6098     case VariantCourier:
6099       pieces = CourierArray;
6100       gameInfo.boardWidth  = 12;
6101       nrCastlingRights = 0;
6102       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6103       break;
6104     case VariantKnightmate:
6105       pieces = KnightmateArray;
6106       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6107       break;
6108     case VariantSpartan:
6109       pieces = SpartanArray;
6110       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6111       break;
6112     case VariantLion:
6113       pieces = lionArray;
6114       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6115       break;
6116     case VariantChuChess:
6117       pieces = ChuChessArray;
6118       gameInfo.boardWidth = 10;
6119       gameInfo.boardHeight = 10;
6120       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6121       break;
6122     case VariantFairy:
6123       pieces = fairyArray;
6124       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6125       break;
6126     case VariantGreat:
6127       pieces = GreatArray;
6128       gameInfo.boardWidth = 10;
6129       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6130       gameInfo.holdingsSize = 8;
6131       break;
6132     case VariantSuper:
6133       pieces = FIDEArray;
6134       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6135       gameInfo.holdingsSize = 8;
6136       startedFromSetupPosition = TRUE;
6137       break;
6138     case VariantCrazyhouse:
6139     case VariantBughouse:
6140       pieces = FIDEArray;
6141       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6142       gameInfo.holdingsSize = 5;
6143       break;
6144     case VariantWildCastle:
6145       pieces = FIDEArray;
6146       /* !!?shuffle with kings guaranteed to be on d or e file */
6147       shuffleOpenings = 1;
6148       break;
6149     case VariantNoCastle:
6150       pieces = FIDEArray;
6151       nrCastlingRights = 0;
6152       /* !!?unconstrained back-rank shuffle */
6153       shuffleOpenings = 1;
6154       break;
6155     }
6156
6157     overrule = 0;
6158     if(appData.NrFiles >= 0) {
6159         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6160         gameInfo.boardWidth = appData.NrFiles;
6161     }
6162     if(appData.NrRanks >= 0) {
6163         gameInfo.boardHeight = appData.NrRanks;
6164     }
6165     if(appData.holdingsSize >= 0) {
6166         i = appData.holdingsSize;
6167         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6168         gameInfo.holdingsSize = i;
6169     }
6170     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6171     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6172         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6173
6174     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6175     if(pawnRow < 1) pawnRow = 1;
6176     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6177        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6178     if(gameInfo.variant == VariantChu) pawnRow = 3;
6179
6180     /* User pieceToChar list overrules defaults */
6181     if(appData.pieceToCharTable != NULL)
6182         SetCharTable(pieceToChar, appData.pieceToCharTable);
6183
6184     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6185
6186         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6187             s = (ChessSquare) 0; /* account holding counts in guard band */
6188         for( i=0; i<BOARD_HEIGHT; i++ )
6189             initialPosition[i][j] = s;
6190
6191         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6192         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6193         initialPosition[pawnRow][j] = WhitePawn;
6194         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6195         if(gameInfo.variant == VariantXiangqi) {
6196             if(j&1) {
6197                 initialPosition[pawnRow][j] =
6198                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6199                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6200                    initialPosition[2][j] = WhiteCannon;
6201                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6202                 }
6203             }
6204         }
6205         if(gameInfo.variant == VariantChu) {
6206              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6207                initialPosition[pawnRow+1][j] = WhiteCobra,
6208                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6209              for(i=1; i<pieceRows; i++) {
6210                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6211                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6212              }
6213         }
6214         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6215             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6216                initialPosition[0][j] = WhiteRook;
6217                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6218             }
6219         }
6220         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6221     }
6222     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6223     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6224
6225             j=BOARD_LEFT+1;
6226             initialPosition[1][j] = WhiteBishop;
6227             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6228             j=BOARD_RGHT-2;
6229             initialPosition[1][j] = WhiteRook;
6230             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6231     }
6232
6233     if( nrCastlingRights == -1) {
6234         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6235         /*       This sets default castling rights from none to normal corners   */
6236         /* Variants with other castling rights must set them themselves above    */
6237         nrCastlingRights = 6;
6238
6239         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6240         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6241         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6242         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6243         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6244         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6245      }
6246
6247      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6248      if(gameInfo.variant == VariantGreat) { // promotion commoners
6249         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6250         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6251         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6252         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6253      }
6254      if( gameInfo.variant == VariantSChess ) {
6255       initialPosition[1][0] = BlackMarshall;
6256       initialPosition[2][0] = BlackAngel;
6257       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6258       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6259       initialPosition[1][1] = initialPosition[2][1] =
6260       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6261      }
6262   if (appData.debugMode) {
6263     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6264   }
6265     if(shuffleOpenings) {
6266         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6267         startedFromSetupPosition = TRUE;
6268     }
6269     if(startedFromPositionFile) {
6270       /* [HGM] loadPos: use PositionFile for every new game */
6271       CopyBoard(initialPosition, filePosition);
6272       for(i=0; i<nrCastlingRights; i++)
6273           initialRights[i] = filePosition[CASTLING][i];
6274       startedFromSetupPosition = TRUE;
6275     }
6276
6277     CopyBoard(boards[0], initialPosition);
6278
6279     if(oldx != gameInfo.boardWidth ||
6280        oldy != gameInfo.boardHeight ||
6281        oldv != gameInfo.variant ||
6282        oldh != gameInfo.holdingsWidth
6283                                          )
6284             InitDrawingSizes(-2 ,0);
6285
6286     oldv = gameInfo.variant;
6287     if (redraw)
6288       DrawPosition(TRUE, boards[currentMove]);
6289 }
6290
6291 void
6292 SendBoard (ChessProgramState *cps, int moveNum)
6293 {
6294     char message[MSG_SIZ];
6295
6296     if (cps->useSetboard) {
6297       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6298       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6299       SendToProgram(message, cps);
6300       free(fen);
6301
6302     } else {
6303       ChessSquare *bp;
6304       int i, j, left=0, right=BOARD_WIDTH;
6305       /* Kludge to set black to move, avoiding the troublesome and now
6306        * deprecated "black" command.
6307        */
6308       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6309         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6310
6311       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6312
6313       SendToProgram("edit\n", cps);
6314       SendToProgram("#\n", cps);
6315       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6316         bp = &boards[moveNum][i][left];
6317         for (j = left; j < right; j++, bp++) {
6318           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6319           if ((int) *bp < (int) BlackPawn) {
6320             if(j == BOARD_RGHT+1)
6321                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6322             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6323             if(message[0] == '+' || message[0] == '~') {
6324               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6325                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6326                         AAA + j, ONE + i);
6327             }
6328             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6329                 message[1] = BOARD_RGHT   - 1 - j + '1';
6330                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6331             }
6332             SendToProgram(message, cps);
6333           }
6334         }
6335       }
6336
6337       SendToProgram("c\n", cps);
6338       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6339         bp = &boards[moveNum][i][left];
6340         for (j = left; j < right; j++, bp++) {
6341           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6342           if (((int) *bp != (int) EmptySquare)
6343               && ((int) *bp >= (int) BlackPawn)) {
6344             if(j == BOARD_LEFT-2)
6345                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6346             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6347                     AAA + j, ONE + i);
6348             if(message[0] == '+' || message[0] == '~') {
6349               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6350                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6351                         AAA + j, ONE + i);
6352             }
6353             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6354                 message[1] = BOARD_RGHT   - 1 - j + '1';
6355                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6356             }
6357             SendToProgram(message, cps);
6358           }
6359         }
6360       }
6361
6362       SendToProgram(".\n", cps);
6363     }
6364     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6365 }
6366
6367 char exclusionHeader[MSG_SIZ];
6368 int exCnt, excludePtr;
6369 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6370 static Exclusion excluTab[200];
6371 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6372
6373 static void
6374 WriteMap (int s)
6375 {
6376     int j;
6377     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6378     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6379 }
6380
6381 static void
6382 ClearMap ()
6383 {
6384     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6385     excludePtr = 24; exCnt = 0;
6386     WriteMap(0);
6387 }
6388
6389 static void
6390 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6391 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6392     char buf[2*MOVE_LEN], *p;
6393     Exclusion *e = excluTab;
6394     int i;
6395     for(i=0; i<exCnt; i++)
6396         if(e[i].ff == fromX && e[i].fr == fromY &&
6397            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6398     if(i == exCnt) { // was not in exclude list; add it
6399         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6400         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6401             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6402             return; // abort
6403         }
6404         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6405         excludePtr++; e[i].mark = excludePtr++;
6406         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6407         exCnt++;
6408     }
6409     exclusionHeader[e[i].mark] = state;
6410 }
6411
6412 static int
6413 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6414 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6415     char buf[MSG_SIZ];
6416     int j, k;
6417     ChessMove moveType;
6418     if((signed char)promoChar == -1) { // kludge to indicate best move
6419         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6420             return 1; // if unparsable, abort
6421     }
6422     // update exclusion map (resolving toggle by consulting existing state)
6423     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6424     j = k%8; k >>= 3;
6425     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6426     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6427          excludeMap[k] |=   1<<j;
6428     else excludeMap[k] &= ~(1<<j);
6429     // update header
6430     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6431     // inform engine
6432     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6433     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6434     SendToBoth(buf);
6435     return (state == '+');
6436 }
6437
6438 static void
6439 ExcludeClick (int index)
6440 {
6441     int i, j;
6442     Exclusion *e = excluTab;
6443     if(index < 25) { // none, best or tail clicked
6444         if(index < 13) { // none: include all
6445             WriteMap(0); // clear map
6446             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6447             SendToBoth("include all\n"); // and inform engine
6448         } else if(index > 18) { // tail
6449             if(exclusionHeader[19] == '-') { // tail was excluded
6450                 SendToBoth("include all\n");
6451                 WriteMap(0); // clear map completely
6452                 // now re-exclude selected moves
6453                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6454                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6455             } else { // tail was included or in mixed state
6456                 SendToBoth("exclude all\n");
6457                 WriteMap(0xFF); // fill map completely
6458                 // now re-include selected moves
6459                 j = 0; // count them
6460                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6461                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6462                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6463             }
6464         } else { // best
6465             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6466         }
6467     } else {
6468         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6469             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6470             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6471             break;
6472         }
6473     }
6474 }
6475
6476 ChessSquare
6477 DefaultPromoChoice (int white)
6478 {
6479     ChessSquare result;
6480     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6481        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6482         result = WhiteFerz; // no choice
6483     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6484         result= WhiteKing; // in Suicide Q is the last thing we want
6485     else if(gameInfo.variant == VariantSpartan)
6486         result = white ? WhiteQueen : WhiteAngel;
6487     else result = WhiteQueen;
6488     if(!white) result = WHITE_TO_BLACK result;
6489     return result;
6490 }
6491
6492 static int autoQueen; // [HGM] oneclick
6493
6494 int
6495 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6496 {
6497     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6498     /* [HGM] add Shogi promotions */
6499     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6500     ChessSquare piece, partner;
6501     ChessMove moveType;
6502     Boolean premove;
6503
6504     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6505     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6506
6507     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6508       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6509         return FALSE;
6510
6511     piece = boards[currentMove][fromY][fromX];
6512     if(gameInfo.variant == VariantChu) {
6513         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6514         promotionZoneSize = BOARD_HEIGHT/3;
6515         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6516     } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
6517         promotionZoneSize = BOARD_HEIGHT/3;
6518         highestPromotingPiece = (int)WhiteAlfil;
6519     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6520         promotionZoneSize = 3;
6521     }
6522
6523     // Treat Lance as Pawn when it is not representing Amazon
6524     if(gameInfo.variant != VariantSuper) {
6525         if(piece == WhiteLance) piece = WhitePawn; else
6526         if(piece == BlackLance) piece = BlackPawn;
6527     }
6528
6529     // next weed out all moves that do not touch the promotion zone at all
6530     if((int)piece >= BlackPawn) {
6531         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6532              return FALSE;
6533         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6534         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6535     } else {
6536         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6537            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6538         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6539              return FALSE;
6540     }
6541
6542     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6543
6544     // weed out mandatory Shogi promotions
6545     if(gameInfo.variant == VariantShogi) {
6546         if(piece >= BlackPawn) {
6547             if(toY == 0 && piece == BlackPawn ||
6548                toY == 0 && piece == BlackQueen ||
6549                toY <= 1 && piece == BlackKnight) {
6550                 *promoChoice = '+';
6551                 return FALSE;
6552             }
6553         } else {
6554             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6555                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6556                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6557                 *promoChoice = '+';
6558                 return FALSE;
6559             }
6560         }
6561     }
6562
6563     // weed out obviously illegal Pawn moves
6564     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6565         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6566         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6567         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6568         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6569         // note we are not allowed to test for valid (non-)capture, due to premove
6570     }
6571
6572     // we either have a choice what to promote to, or (in Shogi) whether to promote
6573     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6574        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6575         ChessSquare p=BlackFerz;  // no choice
6576         while(p < EmptySquare) {  //but make sure we use piece that exists
6577             *promoChoice = PieceToChar(p++);
6578             if(*promoChoice != '.') break;
6579         }
6580         return FALSE;
6581     }
6582     // no sense asking what we must promote to if it is going to explode...
6583     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6584         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6585         return FALSE;
6586     }
6587     // give caller the default choice even if we will not make it
6588     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6589     partner = piece; // pieces can promote if the pieceToCharTable says so
6590     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6591     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6592     if(        sweepSelect && gameInfo.variant != VariantGreat
6593                            && gameInfo.variant != VariantGrand
6594                            && gameInfo.variant != VariantSuper) return FALSE;
6595     if(autoQueen) return FALSE; // predetermined
6596
6597     // suppress promotion popup on illegal moves that are not premoves
6598     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6599               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6600     if(appData.testLegality && !premove) {
6601         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6602                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6603         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6604         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6605             return FALSE;
6606     }
6607
6608     return TRUE;
6609 }
6610
6611 int
6612 InPalace (int row, int column)
6613 {   /* [HGM] for Xiangqi */
6614     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6615          column < (BOARD_WIDTH + 4)/2 &&
6616          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6617     return FALSE;
6618 }
6619
6620 int
6621 PieceForSquare (int x, int y)
6622 {
6623   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6624      return -1;
6625   else
6626      return boards[currentMove][y][x];
6627 }
6628
6629 int
6630 OKToStartUserMove (int x, int y)
6631 {
6632     ChessSquare from_piece;
6633     int white_piece;
6634
6635     if (matchMode) return FALSE;
6636     if (gameMode == EditPosition) return TRUE;
6637
6638     if (x >= 0 && y >= 0)
6639       from_piece = boards[currentMove][y][x];
6640     else
6641       from_piece = EmptySquare;
6642
6643     if (from_piece == EmptySquare) return FALSE;
6644
6645     white_piece = (int)from_piece >= (int)WhitePawn &&
6646       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6647
6648     switch (gameMode) {
6649       case AnalyzeFile:
6650       case TwoMachinesPlay:
6651       case EndOfGame:
6652         return FALSE;
6653
6654       case IcsObserving:
6655       case IcsIdle:
6656         return FALSE;
6657
6658       case MachinePlaysWhite:
6659       case IcsPlayingBlack:
6660         if (appData.zippyPlay) return FALSE;
6661         if (white_piece) {
6662             DisplayMoveError(_("You are playing Black"));
6663             return FALSE;
6664         }
6665         break;
6666
6667       case MachinePlaysBlack:
6668       case IcsPlayingWhite:
6669         if (appData.zippyPlay) return FALSE;
6670         if (!white_piece) {
6671             DisplayMoveError(_("You are playing White"));
6672             return FALSE;
6673         }
6674         break;
6675
6676       case PlayFromGameFile:
6677             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6678       case EditGame:
6679         if (!white_piece && WhiteOnMove(currentMove)) {
6680             DisplayMoveError(_("It is White's turn"));
6681             return FALSE;
6682         }
6683         if (white_piece && !WhiteOnMove(currentMove)) {
6684             DisplayMoveError(_("It is Black's turn"));
6685             return FALSE;
6686         }
6687         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6688             /* Editing correspondence game history */
6689             /* Could disallow this or prompt for confirmation */
6690             cmailOldMove = -1;
6691         }
6692         break;
6693
6694       case BeginningOfGame:
6695         if (appData.icsActive) return FALSE;
6696         if (!appData.noChessProgram) {
6697             if (!white_piece) {
6698                 DisplayMoveError(_("You are playing White"));
6699                 return FALSE;
6700             }
6701         }
6702         break;
6703
6704       case Training:
6705         if (!white_piece && WhiteOnMove(currentMove)) {
6706             DisplayMoveError(_("It is White's turn"));
6707             return FALSE;
6708         }
6709         if (white_piece && !WhiteOnMove(currentMove)) {
6710             DisplayMoveError(_("It is Black's turn"));
6711             return FALSE;
6712         }
6713         break;
6714
6715       default:
6716       case IcsExamining:
6717         break;
6718     }
6719     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6720         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6721         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6722         && gameMode != AnalyzeFile && gameMode != Training) {
6723         DisplayMoveError(_("Displayed position is not current"));
6724         return FALSE;
6725     }
6726     return TRUE;
6727 }
6728
6729 Boolean
6730 OnlyMove (int *x, int *y, Boolean captures)
6731 {
6732     DisambiguateClosure cl;
6733     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6734     switch(gameMode) {
6735       case MachinePlaysBlack:
6736       case IcsPlayingWhite:
6737       case BeginningOfGame:
6738         if(!WhiteOnMove(currentMove)) return FALSE;
6739         break;
6740       case MachinePlaysWhite:
6741       case IcsPlayingBlack:
6742         if(WhiteOnMove(currentMove)) return FALSE;
6743         break;
6744       case EditGame:
6745         break;
6746       default:
6747         return FALSE;
6748     }
6749     cl.pieceIn = EmptySquare;
6750     cl.rfIn = *y;
6751     cl.ffIn = *x;
6752     cl.rtIn = -1;
6753     cl.ftIn = -1;
6754     cl.promoCharIn = NULLCHAR;
6755     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6756     if( cl.kind == NormalMove ||
6757         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6758         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6759         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6760       fromX = cl.ff;
6761       fromY = cl.rf;
6762       *x = cl.ft;
6763       *y = cl.rt;
6764       return TRUE;
6765     }
6766     if(cl.kind != ImpossibleMove) return FALSE;
6767     cl.pieceIn = EmptySquare;
6768     cl.rfIn = -1;
6769     cl.ffIn = -1;
6770     cl.rtIn = *y;
6771     cl.ftIn = *x;
6772     cl.promoCharIn = NULLCHAR;
6773     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6774     if( cl.kind == NormalMove ||
6775         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6776         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6777         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6778       fromX = cl.ff;
6779       fromY = cl.rf;
6780       *x = cl.ft;
6781       *y = cl.rt;
6782       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6783       return TRUE;
6784     }
6785     return FALSE;
6786 }
6787
6788 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6789 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6790 int lastLoadGameUseList = FALSE;
6791 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6792 ChessMove lastLoadGameStart = EndOfFile;
6793 int doubleClick;
6794
6795 void
6796 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6797 {
6798     ChessMove moveType;
6799     ChessSquare pup;
6800     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6801
6802     /* Check if the user is playing in turn.  This is complicated because we
6803        let the user "pick up" a piece before it is his turn.  So the piece he
6804        tried to pick up may have been captured by the time he puts it down!
6805        Therefore we use the color the user is supposed to be playing in this
6806        test, not the color of the piece that is currently on the starting
6807        square---except in EditGame mode, where the user is playing both
6808        sides; fortunately there the capture race can't happen.  (It can
6809        now happen in IcsExamining mode, but that's just too bad.  The user
6810        will get a somewhat confusing message in that case.)
6811        */
6812
6813     switch (gameMode) {
6814       case AnalyzeFile:
6815       case TwoMachinesPlay:
6816       case EndOfGame:
6817       case IcsObserving:
6818       case IcsIdle:
6819         /* We switched into a game mode where moves are not accepted,
6820            perhaps while the mouse button was down. */
6821         return;
6822
6823       case MachinePlaysWhite:
6824         /* User is moving for Black */
6825         if (WhiteOnMove(currentMove)) {
6826             DisplayMoveError(_("It is White's turn"));
6827             return;
6828         }
6829         break;
6830
6831       case MachinePlaysBlack:
6832         /* User is moving for White */
6833         if (!WhiteOnMove(currentMove)) {
6834             DisplayMoveError(_("It is Black's turn"));
6835             return;
6836         }
6837         break;
6838
6839       case PlayFromGameFile:
6840             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6841       case EditGame:
6842       case IcsExamining:
6843       case BeginningOfGame:
6844       case AnalyzeMode:
6845       case Training:
6846         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6847         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6848             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6849             /* User is moving for Black */
6850             if (WhiteOnMove(currentMove)) {
6851                 DisplayMoveError(_("It is White's turn"));
6852                 return;
6853             }
6854         } else {
6855             /* User is moving for White */
6856             if (!WhiteOnMove(currentMove)) {
6857                 DisplayMoveError(_("It is Black's turn"));
6858                 return;
6859             }
6860         }
6861         break;
6862
6863       case IcsPlayingBlack:
6864         /* User is moving for Black */
6865         if (WhiteOnMove(currentMove)) {
6866             if (!appData.premove) {
6867                 DisplayMoveError(_("It is White's turn"));
6868             } else if (toX >= 0 && toY >= 0) {
6869                 premoveToX = toX;
6870                 premoveToY = toY;
6871                 premoveFromX = fromX;
6872                 premoveFromY = fromY;
6873                 premovePromoChar = promoChar;
6874                 gotPremove = 1;
6875                 if (appData.debugMode)
6876                     fprintf(debugFP, "Got premove: fromX %d,"
6877                             "fromY %d, toX %d, toY %d\n",
6878                             fromX, fromY, toX, toY);
6879             }
6880             return;
6881         }
6882         break;
6883
6884       case IcsPlayingWhite:
6885         /* User is moving for White */
6886         if (!WhiteOnMove(currentMove)) {
6887             if (!appData.premove) {
6888                 DisplayMoveError(_("It is Black's turn"));
6889             } else if (toX >= 0 && toY >= 0) {
6890                 premoveToX = toX;
6891                 premoveToY = toY;
6892                 premoveFromX = fromX;
6893                 premoveFromY = fromY;
6894                 premovePromoChar = promoChar;
6895                 gotPremove = 1;
6896                 if (appData.debugMode)
6897                     fprintf(debugFP, "Got premove: fromX %d,"
6898                             "fromY %d, toX %d, toY %d\n",
6899                             fromX, fromY, toX, toY);
6900             }
6901             return;
6902         }
6903         break;
6904
6905       default:
6906         break;
6907
6908       case EditPosition:
6909         /* EditPosition, empty square, or different color piece;
6910            click-click move is possible */
6911         if (toX == -2 || toY == -2) {
6912             boards[0][fromY][fromX] = EmptySquare;
6913             DrawPosition(FALSE, boards[currentMove]);
6914             return;
6915         } else if (toX >= 0 && toY >= 0) {
6916             boards[0][toY][toX] = boards[0][fromY][fromX];
6917             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6918                 if(boards[0][fromY][0] != EmptySquare) {
6919                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6920                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6921                 }
6922             } else
6923             if(fromX == BOARD_RGHT+1) {
6924                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6925                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6926                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6927                 }
6928             } else
6929             boards[0][fromY][fromX] = gatingPiece;
6930             DrawPosition(FALSE, boards[currentMove]);
6931             return;
6932         }
6933         return;
6934     }
6935
6936     if(toX < 0 || toY < 0) return;
6937     pup = boards[currentMove][toY][toX];
6938
6939     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6940     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6941          if( pup != EmptySquare ) return;
6942          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6943            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6944                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6945            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6946            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6947            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6948            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6949          fromY = DROP_RANK;
6950     }
6951
6952     /* [HGM] always test for legality, to get promotion info */
6953     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6954                                          fromY, fromX, toY, toX, promoChar);
6955
6956     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6957
6958     /* [HGM] but possibly ignore an IllegalMove result */
6959     if (appData.testLegality) {
6960         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6961             DisplayMoveError(_("Illegal move"));
6962             return;
6963         }
6964     }
6965
6966     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6967         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6968              ClearPremoveHighlights(); // was included
6969         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6970         return;
6971     }
6972
6973     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6974 }
6975
6976 /* Common tail of UserMoveEvent and DropMenuEvent */
6977 int
6978 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6979 {
6980     char *bookHit = 0;
6981
6982     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6983         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6984         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6985         if(WhiteOnMove(currentMove)) {
6986             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6987         } else {
6988             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6989         }
6990     }
6991
6992     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6993        move type in caller when we know the move is a legal promotion */
6994     if(moveType == NormalMove && promoChar)
6995         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6996
6997     /* [HGM] <popupFix> The following if has been moved here from
6998        UserMoveEvent(). Because it seemed to belong here (why not allow
6999        piece drops in training games?), and because it can only be
7000        performed after it is known to what we promote. */
7001     if (gameMode == Training) {
7002       /* compare the move played on the board to the next move in the
7003        * game. If they match, display the move and the opponent's response.
7004        * If they don't match, display an error message.
7005        */
7006       int saveAnimate;
7007       Board testBoard;
7008       CopyBoard(testBoard, boards[currentMove]);
7009       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7010
7011       if (CompareBoards(testBoard, boards[currentMove+1])) {
7012         ForwardInner(currentMove+1);
7013
7014         /* Autoplay the opponent's response.
7015          * if appData.animate was TRUE when Training mode was entered,
7016          * the response will be animated.
7017          */
7018         saveAnimate = appData.animate;
7019         appData.animate = animateTraining;
7020         ForwardInner(currentMove+1);
7021         appData.animate = saveAnimate;
7022
7023         /* check for the end of the game */
7024         if (currentMove >= forwardMostMove) {
7025           gameMode = PlayFromGameFile;
7026           ModeHighlight();
7027           SetTrainingModeOff();
7028           DisplayInformation(_("End of game"));
7029         }
7030       } else {
7031         DisplayError(_("Incorrect move"), 0);
7032       }
7033       return 1;
7034     }
7035
7036   /* Ok, now we know that the move is good, so we can kill
7037      the previous line in Analysis Mode */
7038   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7039                                 && currentMove < forwardMostMove) {
7040     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7041     else forwardMostMove = currentMove;
7042   }
7043
7044   ClearMap();
7045
7046   /* If we need the chess program but it's dead, restart it */
7047   ResurrectChessProgram();
7048
7049   /* A user move restarts a paused game*/
7050   if (pausing)
7051     PauseEvent();
7052
7053   thinkOutput[0] = NULLCHAR;
7054
7055   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7056
7057   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7058     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7059     return 1;
7060   }
7061
7062   if (gameMode == BeginningOfGame) {
7063     if (appData.noChessProgram) {
7064       gameMode = EditGame;
7065       SetGameInfo();
7066     } else {
7067       char buf[MSG_SIZ];
7068       gameMode = MachinePlaysBlack;
7069       StartClocks();
7070       SetGameInfo();
7071       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7072       DisplayTitle(buf);
7073       if (first.sendName) {
7074         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7075         SendToProgram(buf, &first);
7076       }
7077       StartClocks();
7078     }
7079     ModeHighlight();
7080   }
7081
7082   /* Relay move to ICS or chess engine */
7083   if (appData.icsActive) {
7084     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7085         gameMode == IcsExamining) {
7086       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7087         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7088         SendToICS("draw ");
7089         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7090       }
7091       // also send plain move, in case ICS does not understand atomic claims
7092       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7093       ics_user_moved = 1;
7094     }
7095   } else {
7096     if (first.sendTime && (gameMode == BeginningOfGame ||
7097                            gameMode == MachinePlaysWhite ||
7098                            gameMode == MachinePlaysBlack)) {
7099       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7100     }
7101     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7102          // [HGM] book: if program might be playing, let it use book
7103         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7104         first.maybeThinking = TRUE;
7105     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7106         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7107         SendBoard(&first, currentMove+1);
7108         if(second.analyzing) {
7109             if(!second.useSetboard) SendToProgram("undo\n", &second);
7110             SendBoard(&second, currentMove+1);
7111         }
7112     } else {
7113         SendMoveToProgram(forwardMostMove-1, &first);
7114         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7115     }
7116     if (currentMove == cmailOldMove + 1) {
7117       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7118     }
7119   }
7120
7121   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7122
7123   switch (gameMode) {
7124   case EditGame:
7125     if(appData.testLegality)
7126     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7127     case MT_NONE:
7128     case MT_CHECK:
7129       break;
7130     case MT_CHECKMATE:
7131     case MT_STAINMATE:
7132       if (WhiteOnMove(currentMove)) {
7133         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7134       } else {
7135         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7136       }
7137       break;
7138     case MT_STALEMATE:
7139       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7140       break;
7141     }
7142     break;
7143
7144   case MachinePlaysBlack:
7145   case MachinePlaysWhite:
7146     /* disable certain menu options while machine is thinking */
7147     SetMachineThinkingEnables();
7148     break;
7149
7150   default:
7151     break;
7152   }
7153
7154   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7155   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7156
7157   if(bookHit) { // [HGM] book: simulate book reply
7158         static char bookMove[MSG_SIZ]; // a bit generous?
7159
7160         programStats.nodes = programStats.depth = programStats.time =
7161         programStats.score = programStats.got_only_move = 0;
7162         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7163
7164         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7165         strcat(bookMove, bookHit);
7166         HandleMachineMove(bookMove, &first);
7167   }
7168   return 1;
7169 }
7170
7171 void
7172 MarkByFEN(char *fen)
7173 {
7174         int r, f;
7175         if(!appData.markers || !appData.highlightDragging) return;
7176         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7177         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7178         while(*fen) {
7179             int s = 0;
7180             marker[r][f] = 0;
7181             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7182             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7183             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7184             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7185             if(*fen == 'T') marker[r][f++] = 0; else
7186             if(*fen == 'Y') marker[r][f++] = 1; else
7187             if(*fen == 'G') marker[r][f++] = 3; else
7188             if(*fen == 'B') marker[r][f++] = 4; else
7189             if(*fen == 'C') marker[r][f++] = 5; else
7190             if(*fen == 'M') marker[r][f++] = 6; else
7191             if(*fen == 'W') marker[r][f++] = 7; else
7192             if(*fen == 'D') marker[r][f++] = 8; else
7193             if(*fen == 'R') marker[r][f++] = 2; else {
7194                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7195               f += s; fen -= s>0;
7196             }
7197             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7198             if(r < 0) break;
7199             fen++;
7200         }
7201         DrawPosition(TRUE, NULL);
7202 }
7203
7204 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7205
7206 void
7207 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7208 {
7209     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7210     Markers *m = (Markers *) closure;
7211     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
7212         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7213                          || kind == WhiteCapturesEnPassant
7214                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
7215     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
7216 }
7217
7218 static int hoverSavedValid;
7219
7220 void
7221 MarkTargetSquares (int clear)
7222 {
7223   int x, y, sum=0;
7224   if(clear) { // no reason to ever suppress clearing
7225     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7226     hoverSavedValid = 0;
7227     if(!sum) return; // nothing was cleared,no redraw needed
7228   } else {
7229     int capt = 0;
7230     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7231        !appData.testLegality || gameMode == EditPosition) return;
7232     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7233     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7234       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7235       if(capt)
7236       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7237     }
7238   }
7239   DrawPosition(FALSE, NULL);
7240 }
7241
7242 int
7243 Explode (Board board, int fromX, int fromY, int toX, int toY)
7244 {
7245     if(gameInfo.variant == VariantAtomic &&
7246        (board[toY][toX] != EmptySquare ||                     // capture?
7247         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7248                          board[fromY][fromX] == BlackPawn   )
7249       )) {
7250         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7251         return TRUE;
7252     }
7253     return FALSE;
7254 }
7255
7256 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7257
7258 int
7259 CanPromote (ChessSquare piece, int y)
7260 {
7261         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7262         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7263         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7264         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7265            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7266            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7267          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7268         return (piece == BlackPawn && y <= zone ||
7269                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7270                 piece == BlackLance && y == 1 ||
7271                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7272 }
7273
7274 void
7275 HoverEvent (int xPix, int yPix, int x, int y)
7276 {
7277         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7278         int r, f;
7279         if(!first.highlight) return;
7280         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7281         if(x == oldX && y == oldY) return; // only do something if we enter new square
7282         oldFromX = fromX; oldFromY = fromY;
7283         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7284           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7285             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7286           hoverSavedValid = 1;
7287         } else if(oldX != x || oldY != y) {
7288           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7289           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7290           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7291             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7292           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7293             char buf[MSG_SIZ];
7294             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7295             SendToProgram(buf, &first);
7296           }
7297           oldX = x; oldY = y;
7298 //        SetHighlights(fromX, fromY, x, y);
7299         }
7300 }
7301
7302 void ReportClick(char *action, int x, int y)
7303 {
7304         char buf[MSG_SIZ]; // Inform engine of what user does
7305         int r, f;
7306         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7307           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
7308         if(!first.highlight || gameMode == EditPosition) return;
7309         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7310         SendToProgram(buf, &first);
7311 }
7312
7313 void
7314 LeftClick (ClickType clickType, int xPix, int yPix)
7315 {
7316     int x, y;
7317     Boolean saveAnimate;
7318     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7319     char promoChoice = NULLCHAR;
7320     ChessSquare piece;
7321     static TimeMark lastClickTime, prevClickTime;
7322
7323     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7324
7325     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7326
7327     if (clickType == Press) ErrorPopDown();
7328     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7329
7330     x = EventToSquare(xPix, BOARD_WIDTH);
7331     y = EventToSquare(yPix, BOARD_HEIGHT);
7332     if (!flipView && y >= 0) {
7333         y = BOARD_HEIGHT - 1 - y;
7334     }
7335     if (flipView && x >= 0) {
7336         x = BOARD_WIDTH - 1 - x;
7337     }
7338
7339     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7340         defaultPromoChoice = promoSweep;
7341         promoSweep = EmptySquare;   // terminate sweep
7342         promoDefaultAltered = TRUE;
7343         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7344     }
7345
7346     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7347         if(clickType == Release) return; // ignore upclick of click-click destination
7348         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7349         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7350         if(gameInfo.holdingsWidth &&
7351                 (WhiteOnMove(currentMove)
7352                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7353                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7354             // click in right holdings, for determining promotion piece
7355             ChessSquare p = boards[currentMove][y][x];
7356             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7357             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7358             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7359                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7360                 fromX = fromY = -1;
7361                 return;
7362             }
7363         }
7364         DrawPosition(FALSE, boards[currentMove]);
7365         return;
7366     }
7367
7368     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7369     if(clickType == Press
7370             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7371               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7372               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7373         return;
7374
7375     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7376         // could be static click on premove from-square: abort premove
7377         gotPremove = 0;
7378         ClearPremoveHighlights();
7379     }
7380
7381     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7382         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7383
7384     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7385         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7386                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7387         defaultPromoChoice = DefaultPromoChoice(side);
7388     }
7389
7390     autoQueen = appData.alwaysPromoteToQueen;
7391
7392     if (fromX == -1) {
7393       int originalY = y;
7394       gatingPiece = EmptySquare;
7395       if (clickType != Press) {
7396         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7397             DragPieceEnd(xPix, yPix); dragging = 0;
7398             DrawPosition(FALSE, NULL);
7399         }
7400         return;
7401       }
7402       doubleClick = FALSE;
7403       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7404         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7405       }
7406       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7407       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7408          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7409          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7410             /* First square */
7411             if (OKToStartUserMove(fromX, fromY)) {
7412                 second = 0;
7413                 ReportClick("lift", x, y);
7414                 MarkTargetSquares(0);
7415                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7416                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7417                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7418                     promoSweep = defaultPromoChoice;
7419                     selectFlag = 0; lastX = xPix; lastY = yPix;
7420                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7421                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7422                 }
7423                 if (appData.highlightDragging) {
7424                     SetHighlights(fromX, fromY, -1, -1);
7425                 } else {
7426                     ClearHighlights();
7427                 }
7428             } else fromX = fromY = -1;
7429             return;
7430         }
7431     }
7432
7433     /* fromX != -1 */
7434     if (clickType == Press && gameMode != EditPosition) {
7435         ChessSquare fromP;
7436         ChessSquare toP;
7437         int frc;
7438
7439         // ignore off-board to clicks
7440         if(y < 0 || x < 0) return;
7441
7442         /* Check if clicking again on the same color piece */
7443         fromP = boards[currentMove][fromY][fromX];
7444         toP = boards[currentMove][y][x];
7445         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7446         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7447            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7448              WhitePawn <= toP && toP <= WhiteKing &&
7449              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7450              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7451             (BlackPawn <= fromP && fromP <= BlackKing &&
7452              BlackPawn <= toP && toP <= BlackKing &&
7453              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7454              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7455             /* Clicked again on same color piece -- changed his mind */
7456             second = (x == fromX && y == fromY);
7457             killX = killY = -1;
7458             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7459                 second = FALSE; // first double-click rather than scond click
7460                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7461             }
7462             promoDefaultAltered = FALSE;
7463             MarkTargetSquares(1);
7464            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7465             if (appData.highlightDragging) {
7466                 SetHighlights(x, y, -1, -1);
7467             } else {
7468                 ClearHighlights();
7469             }
7470             if (OKToStartUserMove(x, y)) {
7471                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7472                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7473                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7474                  gatingPiece = boards[currentMove][fromY][fromX];
7475                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7476                 fromX = x;
7477                 fromY = y; dragging = 1;
7478                 ReportClick("lift", x, y);
7479                 MarkTargetSquares(0);
7480                 DragPieceBegin(xPix, yPix, FALSE);
7481                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7482                     promoSweep = defaultPromoChoice;
7483                     selectFlag = 0; lastX = xPix; lastY = yPix;
7484                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7485                 }
7486             }
7487            }
7488            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7489            second = FALSE;
7490         }
7491         // ignore clicks on holdings
7492         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7493     }
7494
7495     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7496         DragPieceEnd(xPix, yPix); dragging = 0;
7497         if(clearFlag) {
7498             // a deferred attempt to click-click move an empty square on top of a piece
7499             boards[currentMove][y][x] = EmptySquare;
7500             ClearHighlights();
7501             DrawPosition(FALSE, boards[currentMove]);
7502             fromX = fromY = -1; clearFlag = 0;
7503             return;
7504         }
7505         if (appData.animateDragging) {
7506             /* Undo animation damage if any */
7507             DrawPosition(FALSE, NULL);
7508         }
7509         if (second || sweepSelecting) {
7510             /* Second up/down in same square; just abort move */
7511             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7512             second = sweepSelecting = 0;
7513             fromX = fromY = -1;
7514             gatingPiece = EmptySquare;
7515             MarkTargetSquares(1);
7516             ClearHighlights();
7517             gotPremove = 0;
7518             ClearPremoveHighlights();
7519         } else {
7520             /* First upclick in same square; start click-click mode */
7521             SetHighlights(x, y, -1, -1);
7522         }
7523         return;
7524     }
7525
7526     clearFlag = 0;
7527
7528     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
7529         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7530         DisplayMessage(_("only marked squares are legal"),"");
7531         DrawPosition(TRUE, NULL);
7532         return; // ignore to-click
7533     }
7534
7535     /* we now have a different from- and (possibly off-board) to-square */
7536     /* Completed move */
7537     if(!sweepSelecting) {
7538         toX = x;
7539         toY = y;
7540     }
7541
7542     piece = boards[currentMove][fromY][fromX];
7543
7544     saveAnimate = appData.animate;
7545     if (clickType == Press) {
7546         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7547         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7548             // must be Edit Position mode with empty-square selected
7549             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7550             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7551             return;
7552         }
7553         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7554             return;
7555         }
7556         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7557             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7558         } else
7559         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7560         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7561           if(appData.sweepSelect) {
7562             promoSweep = defaultPromoChoice;
7563             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7564             selectFlag = 0; lastX = xPix; lastY = yPix;
7565             Sweep(0); // Pawn that is going to promote: preview promotion piece
7566             sweepSelecting = 1;
7567             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7568             MarkTargetSquares(1);
7569           }
7570           return; // promo popup appears on up-click
7571         }
7572         /* Finish clickclick move */
7573         if (appData.animate || appData.highlightLastMove) {
7574             SetHighlights(fromX, fromY, toX, toY);
7575         } else {
7576             ClearHighlights();
7577         }
7578     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7579         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7580         if (appData.animate || appData.highlightLastMove) {
7581             SetHighlights(fromX, fromY, toX, toY);
7582         } else {
7583             ClearHighlights();
7584         }
7585     } else {
7586 #if 0
7587 // [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
7588         /* Finish drag move */
7589         if (appData.highlightLastMove) {
7590             SetHighlights(fromX, fromY, toX, toY);
7591         } else {
7592             ClearHighlights();
7593         }
7594 #endif
7595         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7596         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7597           dragging *= 2;            // flag button-less dragging if we are dragging
7598           MarkTargetSquares(1);
7599           if(x == killX && y == killY) killX = killY = -1; else {
7600             killX = x; killY = y;     //remeber this square as intermediate
7601             ReportClick("put", x, y); // and inform engine
7602             ReportClick("lift", x, y);
7603             MarkTargetSquares(0);
7604             return;
7605           }
7606         }
7607         DragPieceEnd(xPix, yPix); dragging = 0;
7608         /* Don't animate move and drag both */
7609         appData.animate = FALSE;
7610     }
7611
7612     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7613     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7614         ChessSquare piece = boards[currentMove][fromY][fromX];
7615         if(gameMode == EditPosition && piece != EmptySquare &&
7616            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7617             int n;
7618
7619             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7620                 n = PieceToNumber(piece - (int)BlackPawn);
7621                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7622                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7623                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7624             } else
7625             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7626                 n = PieceToNumber(piece);
7627                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7628                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7629                 boards[currentMove][n][BOARD_WIDTH-2]++;
7630             }
7631             boards[currentMove][fromY][fromX] = EmptySquare;
7632         }
7633         ClearHighlights();
7634         fromX = fromY = -1;
7635         MarkTargetSquares(1);
7636         DrawPosition(TRUE, boards[currentMove]);
7637         return;
7638     }
7639
7640     // off-board moves should not be highlighted
7641     if(x < 0 || y < 0) ClearHighlights();
7642     else ReportClick("put", x, y);
7643
7644     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7645
7646     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7647         SetHighlights(fromX, fromY, toX, toY);
7648         MarkTargetSquares(1);
7649         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7650             // [HGM] super: promotion to captured piece selected from holdings
7651             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7652             promotionChoice = TRUE;
7653             // kludge follows to temporarily execute move on display, without promoting yet
7654             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7655             boards[currentMove][toY][toX] = p;
7656             DrawPosition(FALSE, boards[currentMove]);
7657             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7658             boards[currentMove][toY][toX] = q;
7659             DisplayMessage("Click in holdings to choose piece", "");
7660             return;
7661         }
7662         PromotionPopUp(promoChoice);
7663     } else {
7664         int oldMove = currentMove;
7665         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7666         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7667         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7668         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7669            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7670             DrawPosition(TRUE, boards[currentMove]);
7671         MarkTargetSquares(1);
7672         fromX = fromY = -1;
7673     }
7674     appData.animate = saveAnimate;
7675     if (appData.animate || appData.animateDragging) {
7676         /* Undo animation damage if needed */
7677         DrawPosition(FALSE, NULL);
7678     }
7679 }
7680
7681 int
7682 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7683 {   // front-end-free part taken out of PieceMenuPopup
7684     int whichMenu; int xSqr, ySqr;
7685
7686     if(seekGraphUp) { // [HGM] seekgraph
7687         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7688         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7689         return -2;
7690     }
7691
7692     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7693          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7694         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7695         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7696         if(action == Press)   {
7697             originalFlip = flipView;
7698             flipView = !flipView; // temporarily flip board to see game from partners perspective
7699             DrawPosition(TRUE, partnerBoard);
7700             DisplayMessage(partnerStatus, "");
7701             partnerUp = TRUE;
7702         } else if(action == Release) {
7703             flipView = originalFlip;
7704             DrawPosition(TRUE, boards[currentMove]);
7705             partnerUp = FALSE;
7706         }
7707         return -2;
7708     }
7709
7710     xSqr = EventToSquare(x, BOARD_WIDTH);
7711     ySqr = EventToSquare(y, BOARD_HEIGHT);
7712     if (action == Release) {
7713         if(pieceSweep != EmptySquare) {
7714             EditPositionMenuEvent(pieceSweep, toX, toY);
7715             pieceSweep = EmptySquare;
7716         } else UnLoadPV(); // [HGM] pv
7717     }
7718     if (action != Press) return -2; // return code to be ignored
7719     switch (gameMode) {
7720       case IcsExamining:
7721         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7722       case EditPosition:
7723         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7724         if (xSqr < 0 || ySqr < 0) return -1;
7725         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7726         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7727         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7728         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7729         NextPiece(0);
7730         return 2; // grab
7731       case IcsObserving:
7732         if(!appData.icsEngineAnalyze) return -1;
7733       case IcsPlayingWhite:
7734       case IcsPlayingBlack:
7735         if(!appData.zippyPlay) goto noZip;
7736       case AnalyzeMode:
7737       case AnalyzeFile:
7738       case MachinePlaysWhite:
7739       case MachinePlaysBlack:
7740       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7741         if (!appData.dropMenu) {
7742           LoadPV(x, y);
7743           return 2; // flag front-end to grab mouse events
7744         }
7745         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7746            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7747       case EditGame:
7748       noZip:
7749         if (xSqr < 0 || ySqr < 0) return -1;
7750         if (!appData.dropMenu || appData.testLegality &&
7751             gameInfo.variant != VariantBughouse &&
7752             gameInfo.variant != VariantCrazyhouse) return -1;
7753         whichMenu = 1; // drop menu
7754         break;
7755       default:
7756         return -1;
7757     }
7758
7759     if (((*fromX = xSqr) < 0) ||
7760         ((*fromY = ySqr) < 0)) {
7761         *fromX = *fromY = -1;
7762         return -1;
7763     }
7764     if (flipView)
7765       *fromX = BOARD_WIDTH - 1 - *fromX;
7766     else
7767       *fromY = BOARD_HEIGHT - 1 - *fromY;
7768
7769     return whichMenu;
7770 }
7771
7772 void
7773 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7774 {
7775 //    char * hint = lastHint;
7776     FrontEndProgramStats stats;
7777
7778     stats.which = cps == &first ? 0 : 1;
7779     stats.depth = cpstats->depth;
7780     stats.nodes = cpstats->nodes;
7781     stats.score = cpstats->score;
7782     stats.time = cpstats->time;
7783     stats.pv = cpstats->movelist;
7784     stats.hint = lastHint;
7785     stats.an_move_index = 0;
7786     stats.an_move_count = 0;
7787
7788     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7789         stats.hint = cpstats->move_name;
7790         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7791         stats.an_move_count = cpstats->nr_moves;
7792     }
7793
7794     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
7795
7796     SetProgramStats( &stats );
7797 }
7798
7799 void
7800 ClearEngineOutputPane (int which)
7801 {
7802     static FrontEndProgramStats dummyStats;
7803     dummyStats.which = which;
7804     dummyStats.pv = "#";
7805     SetProgramStats( &dummyStats );
7806 }
7807
7808 #define MAXPLAYERS 500
7809
7810 char *
7811 TourneyStandings (int display)
7812 {
7813     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7814     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7815     char result, *p, *names[MAXPLAYERS];
7816
7817     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7818         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7819     names[0] = p = strdup(appData.participants);
7820     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7821
7822     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7823
7824     while(result = appData.results[nr]) {
7825         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7826         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7827         wScore = bScore = 0;
7828         switch(result) {
7829           case '+': wScore = 2; break;
7830           case '-': bScore = 2; break;
7831           case '=': wScore = bScore = 1; break;
7832           case ' ':
7833           case '*': return strdup("busy"); // tourney not finished
7834         }
7835         score[w] += wScore;
7836         score[b] += bScore;
7837         games[w]++;
7838         games[b]++;
7839         nr++;
7840     }
7841     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7842     for(w=0; w<nPlayers; w++) {
7843         bScore = -1;
7844         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7845         ranking[w] = b; points[w] = bScore; score[b] = -2;
7846     }
7847     p = malloc(nPlayers*34+1);
7848     for(w=0; w<nPlayers && w<display; w++)
7849         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7850     free(names[0]);
7851     return p;
7852 }
7853
7854 void
7855 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7856 {       // count all piece types
7857         int p, f, r;
7858         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7859         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7860         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7861                 p = board[r][f];
7862                 pCnt[p]++;
7863                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7864                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7865                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7866                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7867                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7868                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7869         }
7870 }
7871
7872 int
7873 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7874 {
7875         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7876         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7877
7878         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7879         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7880         if(myPawns == 2 && nMine == 3) // KPP
7881             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7882         if(myPawns == 1 && nMine == 2) // KP
7883             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7884         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7885             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7886         if(myPawns) return FALSE;
7887         if(pCnt[WhiteRook+side])
7888             return pCnt[BlackRook-side] ||
7889                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7890                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7891                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7892         if(pCnt[WhiteCannon+side]) {
7893             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7894             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7895         }
7896         if(pCnt[WhiteKnight+side])
7897             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7898         return FALSE;
7899 }
7900
7901 int
7902 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7903 {
7904         VariantClass v = gameInfo.variant;
7905
7906         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7907         if(v == VariantShatranj) return TRUE; // always winnable through baring
7908         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7909         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7910
7911         if(v == VariantXiangqi) {
7912                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7913
7914                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7915                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7916                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7917                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7918                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7919                 if(stale) // we have at least one last-rank P plus perhaps C
7920                     return majors // KPKX
7921                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7922                 else // KCA*E*
7923                     return pCnt[WhiteFerz+side] // KCAK
7924                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7925                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7926                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7927
7928         } else if(v == VariantKnightmate) {
7929                 if(nMine == 1) return FALSE;
7930                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7931         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7932                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7933
7934                 if(nMine == 1) return FALSE; // bare King
7935                 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
7936                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7937                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7938                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7939                 if(pCnt[WhiteKnight+side])
7940                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7941                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7942                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7943                 if(nBishops)
7944                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7945                 if(pCnt[WhiteAlfil+side])
7946                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7947                 if(pCnt[WhiteWazir+side])
7948                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7949         }
7950
7951         return TRUE;
7952 }
7953
7954 int
7955 CompareWithRights (Board b1, Board b2)
7956 {
7957     int rights = 0;
7958     if(!CompareBoards(b1, b2)) return FALSE;
7959     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7960     /* compare castling rights */
7961     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7962            rights++; /* King lost rights, while rook still had them */
7963     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7964         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7965            rights++; /* but at least one rook lost them */
7966     }
7967     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7968            rights++;
7969     if( b1[CASTLING][5] != NoRights ) {
7970         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7971            rights++;
7972     }
7973     return rights == 0;
7974 }
7975
7976 int
7977 Adjudicate (ChessProgramState *cps)
7978 {       // [HGM] some adjudications useful with buggy engines
7979         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7980         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7981         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7982         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7983         int k, drop, count = 0; static int bare = 1;
7984         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7985         Boolean canAdjudicate = !appData.icsActive;
7986
7987         // most tests only when we understand the game, i.e. legality-checking on
7988             if( appData.testLegality )
7989             {   /* [HGM] Some more adjudications for obstinate engines */
7990                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7991                 static int moveCount = 6;
7992                 ChessMove result;
7993                 char *reason = NULL;
7994
7995                 /* Count what is on board. */
7996                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7997
7998                 /* Some material-based adjudications that have to be made before stalemate test */
7999                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8000                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8001                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8002                      if(canAdjudicate && appData.checkMates) {
8003                          if(engineOpponent)
8004                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8005                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8006                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8007                          return 1;
8008                      }
8009                 }
8010
8011                 /* Bare King in Shatranj (loses) or Losers (wins) */
8012                 if( nrW == 1 || nrB == 1) {
8013                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8014                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8015                      if(canAdjudicate && appData.checkMates) {
8016                          if(engineOpponent)
8017                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8018                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8019                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8020                          return 1;
8021                      }
8022                   } else
8023                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8024                   {    /* bare King */
8025                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8026                         if(canAdjudicate && appData.checkMates) {
8027                             /* but only adjudicate if adjudication enabled */
8028                             if(engineOpponent)
8029                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8030                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8031                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8032                             return 1;
8033                         }
8034                   }
8035                 } else bare = 1;
8036
8037
8038             // don't wait for engine to announce game end if we can judge ourselves
8039             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8040               case MT_CHECK:
8041                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8042                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8043                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8044                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8045                             checkCnt++;
8046                         if(checkCnt >= 2) {
8047                             reason = "Xboard adjudication: 3rd check";
8048                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8049                             break;
8050                         }
8051                     }
8052                 }
8053               case MT_NONE:
8054               default:
8055                 break;
8056               case MT_STALEMATE:
8057               case MT_STAINMATE:
8058                 reason = "Xboard adjudication: Stalemate";
8059                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8060                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8061                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8062                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8063                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8064                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8065                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8066                                                                         EP_CHECKMATE : EP_WINS);
8067                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8068                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8069                 }
8070                 break;
8071               case MT_CHECKMATE:
8072                 reason = "Xboard adjudication: Checkmate";
8073                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8074                 if(gameInfo.variant == VariantShogi) {
8075                     if(forwardMostMove > backwardMostMove
8076                        && moveList[forwardMostMove-1][1] == '@'
8077                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8078                         reason = "XBoard adjudication: pawn-drop mate";
8079                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8080                     }
8081                 }
8082                 break;
8083             }
8084
8085                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8086                     case EP_STALEMATE:
8087                         result = GameIsDrawn; break;
8088                     case EP_CHECKMATE:
8089                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8090                     case EP_WINS:
8091                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8092                     default:
8093                         result = EndOfFile;
8094                 }
8095                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8096                     if(engineOpponent)
8097                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8098                     GameEnds( result, reason, GE_XBOARD );
8099                     return 1;
8100                 }
8101
8102                 /* Next absolutely insufficient mating material. */
8103                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8104                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8105                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8106
8107                      /* always flag draws, for judging claims */
8108                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8109
8110                      if(canAdjudicate && appData.materialDraws) {
8111                          /* but only adjudicate them if adjudication enabled */
8112                          if(engineOpponent) {
8113                            SendToProgram("force\n", engineOpponent); // suppress reply
8114                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8115                          }
8116                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8117                          return 1;
8118                      }
8119                 }
8120
8121                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8122                 if(gameInfo.variant == VariantXiangqi ?
8123                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8124                  : nrW + nrB == 4 &&
8125                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8126                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8127                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8128                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8129                    ) ) {
8130                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8131                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8132                           if(engineOpponent) {
8133                             SendToProgram("force\n", engineOpponent); // suppress reply
8134                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8135                           }
8136                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8137                           return 1;
8138                      }
8139                 } else moveCount = 6;
8140             }
8141
8142         // Repetition draws and 50-move rule can be applied independently of legality testing
8143
8144                 /* Check for rep-draws */
8145                 count = 0;
8146                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8147                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8148                 for(k = forwardMostMove-2;
8149                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8150                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8151                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8152                     k-=2)
8153                 {   int rights=0;
8154                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8155                         /* compare castling rights */
8156                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8157                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8158                                 rights++; /* King lost rights, while rook still had them */
8159                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8160                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8161                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8162                                    rights++; /* but at least one rook lost them */
8163                         }
8164                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8165                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8166                                 rights++;
8167                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8168                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8169                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8170                                    rights++;
8171                         }
8172                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8173                             && appData.drawRepeats > 1) {
8174                              /* adjudicate after user-specified nr of repeats */
8175                              int result = GameIsDrawn;
8176                              char *details = "XBoard adjudication: repetition draw";
8177                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8178                                 // [HGM] xiangqi: check for forbidden perpetuals
8179                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8180                                 for(m=forwardMostMove; m>k; m-=2) {
8181                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8182                                         ourPerpetual = 0; // the current mover did not always check
8183                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8184                                         hisPerpetual = 0; // the opponent did not always check
8185                                 }
8186                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8187                                                                         ourPerpetual, hisPerpetual);
8188                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8189                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8190                                     details = "Xboard adjudication: perpetual checking";
8191                                 } else
8192                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8193                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8194                                 } else
8195                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8196                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8197                                         result = BlackWins;
8198                                         details = "Xboard adjudication: repetition";
8199                                     }
8200                                 } else // it must be XQ
8201                                 // Now check for perpetual chases
8202                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8203                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8204                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8205                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8206                                         static char resdet[MSG_SIZ];
8207                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8208                                         details = resdet;
8209                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8210                                     } else
8211                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8212                                         break; // Abort repetition-checking loop.
8213                                 }
8214                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8215                              }
8216                              if(engineOpponent) {
8217                                SendToProgram("force\n", engineOpponent); // suppress reply
8218                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8219                              }
8220                              GameEnds( result, details, GE_XBOARD );
8221                              return 1;
8222                         }
8223                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8224                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8225                     }
8226                 }
8227
8228                 /* Now we test for 50-move draws. Determine ply count */
8229                 count = forwardMostMove;
8230                 /* look for last irreversble move */
8231                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8232                     count--;
8233                 /* if we hit starting position, add initial plies */
8234                 if( count == backwardMostMove )
8235                     count -= initialRulePlies;
8236                 count = forwardMostMove - count;
8237                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8238                         // adjust reversible move counter for checks in Xiangqi
8239                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8240                         if(i < backwardMostMove) i = backwardMostMove;
8241                         while(i <= forwardMostMove) {
8242                                 lastCheck = inCheck; // check evasion does not count
8243                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8244                                 if(inCheck || lastCheck) count--; // check does not count
8245                                 i++;
8246                         }
8247                 }
8248                 if( count >= 100)
8249                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8250                          /* this is used to judge if draw claims are legal */
8251                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8252                          if(engineOpponent) {
8253                            SendToProgram("force\n", engineOpponent); // suppress reply
8254                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8255                          }
8256                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8257                          return 1;
8258                 }
8259
8260                 /* if draw offer is pending, treat it as a draw claim
8261                  * when draw condition present, to allow engines a way to
8262                  * claim draws before making their move to avoid a race
8263                  * condition occurring after their move
8264                  */
8265                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8266                          char *p = NULL;
8267                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8268                              p = "Draw claim: 50-move rule";
8269                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8270                              p = "Draw claim: 3-fold repetition";
8271                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8272                              p = "Draw claim: insufficient mating material";
8273                          if( p != NULL && canAdjudicate) {
8274                              if(engineOpponent) {
8275                                SendToProgram("force\n", engineOpponent); // suppress reply
8276                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8277                              }
8278                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8279                              return 1;
8280                          }
8281                 }
8282
8283                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8284                     if(engineOpponent) {
8285                       SendToProgram("force\n", engineOpponent); // suppress reply
8286                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8287                     }
8288                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8289                     return 1;
8290                 }
8291         return 0;
8292 }
8293
8294 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8295 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8296 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8297
8298 static int
8299 BitbaseProbe ()
8300 {
8301     int pieces[10], squares[10], cnt=0, r, f, res;
8302     static int loaded;
8303     static PPROBE_EGBB probeBB;
8304     if(!appData.testLegality) return 10;
8305     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8306     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8307     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8308     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8309         ChessSquare piece = boards[forwardMostMove][r][f];
8310         int black = (piece >= BlackPawn);
8311         int type = piece - black*BlackPawn;
8312         if(piece == EmptySquare) continue;
8313         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8314         if(type == WhiteKing) type = WhiteQueen + 1;
8315         type = egbbCode[type];
8316         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8317         pieces[cnt] = type + black*6;
8318         if(++cnt > 5) return 11;
8319     }
8320     pieces[cnt] = squares[cnt] = 0;
8321     // probe EGBB
8322     if(loaded == 2) return 13; // loading failed before
8323     if(loaded == 0) {
8324         loaded = 2; // prepare for failure
8325         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8326         HMODULE lib;
8327         PLOAD_EGBB loadBB;
8328         if(!path) return 13; // no egbb installed
8329         strncpy(buf, path + 8, MSG_SIZ);
8330         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8331         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8332         lib = LoadLibrary(buf);
8333         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8334         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8335         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8336         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8337         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8338         loaded = 1; // success!
8339     }
8340     res = probeBB(forwardMostMove & 1, pieces, squares);
8341     return res > 0 ? 1 : res < 0 ? -1 : 0;
8342 }
8343
8344 char *
8345 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8346 {   // [HGM] book: this routine intercepts moves to simulate book replies
8347     char *bookHit = NULL;
8348
8349     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8350         char buf[MSG_SIZ];
8351         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8352         SendToProgram(buf, cps);
8353     }
8354     //first determine if the incoming move brings opponent into his book
8355     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8356         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8357     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8358     if(bookHit != NULL && !cps->bookSuspend) {
8359         // make sure opponent is not going to reply after receiving move to book position
8360         SendToProgram("force\n", cps);
8361         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8362     }
8363     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8364     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8365     // now arrange restart after book miss
8366     if(bookHit) {
8367         // after a book hit we never send 'go', and the code after the call to this routine
8368         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8369         char buf[MSG_SIZ], *move = bookHit;
8370         if(cps->useSAN) {
8371             int fromX, fromY, toX, toY;
8372             char promoChar;
8373             ChessMove moveType;
8374             move = buf + 30;
8375             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8376                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8377                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8378                                     PosFlags(forwardMostMove),
8379                                     fromY, fromX, toY, toX, promoChar, move);
8380             } else {
8381                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8382                 bookHit = NULL;
8383             }
8384         }
8385         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8386         SendToProgram(buf, cps);
8387         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8388     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8389         SendToProgram("go\n", cps);
8390         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8391     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8392         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8393             SendToProgram("go\n", cps);
8394         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8395     }
8396     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8397 }
8398
8399 int
8400 LoadError (char *errmess, ChessProgramState *cps)
8401 {   // unloads engine and switches back to -ncp mode if it was first
8402     if(cps->initDone) return FALSE;
8403     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8404     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8405     cps->pr = NoProc;
8406     if(cps == &first) {
8407         appData.noChessProgram = TRUE;
8408         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8409         gameMode = BeginningOfGame; ModeHighlight();
8410         SetNCPMode();
8411     }
8412     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8413     DisplayMessage("", ""); // erase waiting message
8414     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8415     return TRUE;
8416 }
8417
8418 char *savedMessage;
8419 ChessProgramState *savedState;
8420 void
8421 DeferredBookMove (void)
8422 {
8423         if(savedState->lastPing != savedState->lastPong)
8424                     ScheduleDelayedEvent(DeferredBookMove, 10);
8425         else
8426         HandleMachineMove(savedMessage, savedState);
8427 }
8428
8429 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8430 static ChessProgramState *stalledEngine;
8431 static char stashedInputMove[MSG_SIZ];
8432
8433 void
8434 HandleMachineMove (char *message, ChessProgramState *cps)
8435 {
8436     static char firstLeg[20];
8437     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8438     char realname[MSG_SIZ];
8439     int fromX, fromY, toX, toY;
8440     ChessMove moveType;
8441     char promoChar, roar;
8442     char *p, *pv=buf1;
8443     int machineWhite, oldError;
8444     char *bookHit;
8445
8446     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8447         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8448         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8449             DisplayError(_("Invalid pairing from pairing engine"), 0);
8450             return;
8451         }
8452         pairingReceived = 1;
8453         NextMatchGame();
8454         return; // Skim the pairing messages here.
8455     }
8456
8457     oldError = cps->userError; cps->userError = 0;
8458
8459 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8460     /*
8461      * Kludge to ignore BEL characters
8462      */
8463     while (*message == '\007') message++;
8464
8465     /*
8466      * [HGM] engine debug message: ignore lines starting with '#' character
8467      */
8468     if(cps->debug && *message == '#') return;
8469
8470     /*
8471      * Look for book output
8472      */
8473     if (cps == &first && bookRequested) {
8474         if (message[0] == '\t' || message[0] == ' ') {
8475             /* Part of the book output is here; append it */
8476             strcat(bookOutput, message);
8477             strcat(bookOutput, "  \n");
8478             return;
8479         } else if (bookOutput[0] != NULLCHAR) {
8480             /* All of book output has arrived; display it */
8481             char *p = bookOutput;
8482             while (*p != NULLCHAR) {
8483                 if (*p == '\t') *p = ' ';
8484                 p++;
8485             }
8486             DisplayInformation(bookOutput);
8487             bookRequested = FALSE;
8488             /* Fall through to parse the current output */
8489         }
8490     }
8491
8492     /*
8493      * Look for machine move.
8494      */
8495     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8496         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8497     {
8498         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8499             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8500             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8501             stalledEngine = cps;
8502             if(appData.ponderNextMove) { // bring opponent out of ponder
8503                 if(gameMode == TwoMachinesPlay) {
8504                     if(cps->other->pause)
8505                         PauseEngine(cps->other);
8506                     else
8507                         SendToProgram("easy\n", cps->other);
8508                 }
8509             }
8510             StopClocks();
8511             return;
8512         }
8513
8514         /* This method is only useful on engines that support ping */
8515         if (cps->lastPing != cps->lastPong) {
8516           if (gameMode == BeginningOfGame) {
8517             /* Extra move from before last new; ignore */
8518             if (appData.debugMode) {
8519                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8520             }
8521           } else {
8522             if (appData.debugMode) {
8523                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8524                         cps->which, gameMode);
8525             }
8526
8527             SendToProgram("undo\n", cps);
8528           }
8529           return;
8530         }
8531
8532         switch (gameMode) {
8533           case BeginningOfGame:
8534             /* Extra move from before last reset; ignore */
8535             if (appData.debugMode) {
8536                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8537             }
8538             return;
8539
8540           case EndOfGame:
8541           case IcsIdle:
8542           default:
8543             /* Extra move after we tried to stop.  The mode test is
8544                not a reliable way of detecting this problem, but it's
8545                the best we can do on engines that don't support ping.
8546             */
8547             if (appData.debugMode) {
8548                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8549                         cps->which, gameMode);
8550             }
8551             SendToProgram("undo\n", cps);
8552             return;
8553
8554           case MachinePlaysWhite:
8555           case IcsPlayingWhite:
8556             machineWhite = TRUE;
8557             break;
8558
8559           case MachinePlaysBlack:
8560           case IcsPlayingBlack:
8561             machineWhite = FALSE;
8562             break;
8563
8564           case TwoMachinesPlay:
8565             machineWhite = (cps->twoMachinesColor[0] == 'w');
8566             break;
8567         }
8568         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8569             if (appData.debugMode) {
8570                 fprintf(debugFP,
8571                         "Ignoring move out of turn by %s, gameMode %d"
8572                         ", forwardMost %d\n",
8573                         cps->which, gameMode, forwardMostMove);
8574             }
8575             return;
8576         }
8577
8578         if(cps->alphaRank) AlphaRank(machineMove, 4);
8579
8580         // [HGM] lion: (some very limited) support for Alien protocol
8581         killX = killY = -1;
8582         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8583             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8584             return;
8585         } else if(firstLeg[0]) { // there was a previous leg;
8586             // only support case where same piece makes two step (and don't even test that!)
8587             char buf[20], *p = machineMove+1, *q = buf+1, f;
8588             safeStrCpy(buf, machineMove, 20);
8589             while(isdigit(*q)) q++; // find start of to-square
8590             safeStrCpy(machineMove, firstLeg, 20);
8591             while(isdigit(*p)) p++;
8592             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8593             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8594             firstLeg[0] = NULLCHAR;
8595         }
8596
8597         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8598                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8599             /* Machine move could not be parsed; ignore it. */
8600           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8601                     machineMove, _(cps->which));
8602             DisplayMoveError(buf1);
8603             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8604                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8605             if (gameMode == TwoMachinesPlay) {
8606               GameEnds(machineWhite ? BlackWins : WhiteWins,
8607                        buf1, GE_XBOARD);
8608             }
8609             return;
8610         }
8611
8612         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8613         /* So we have to redo legality test with true e.p. status here,  */
8614         /* to make sure an illegal e.p. capture does not slip through,   */
8615         /* to cause a forfeit on a justified illegal-move complaint      */
8616         /* of the opponent.                                              */
8617         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8618            ChessMove moveType;
8619            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8620                              fromY, fromX, toY, toX, promoChar);
8621             if(moveType == IllegalMove) {
8622               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8623                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8624                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8625                            buf1, GE_XBOARD);
8626                 return;
8627            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8628            /* [HGM] Kludge to handle engines that send FRC-style castling
8629               when they shouldn't (like TSCP-Gothic) */
8630            switch(moveType) {
8631              case WhiteASideCastleFR:
8632              case BlackASideCastleFR:
8633                toX+=2;
8634                currentMoveString[2]++;
8635                break;
8636              case WhiteHSideCastleFR:
8637              case BlackHSideCastleFR:
8638                toX--;
8639                currentMoveString[2]--;
8640                break;
8641              default: ; // nothing to do, but suppresses warning of pedantic compilers
8642            }
8643         }
8644         hintRequested = FALSE;
8645         lastHint[0] = NULLCHAR;
8646         bookRequested = FALSE;
8647         /* Program may be pondering now */
8648         cps->maybeThinking = TRUE;
8649         if (cps->sendTime == 2) cps->sendTime = 1;
8650         if (cps->offeredDraw) cps->offeredDraw--;
8651
8652         /* [AS] Save move info*/
8653         pvInfoList[ forwardMostMove ].score = programStats.score;
8654         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8655         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8656
8657         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8658
8659         /* Test suites abort the 'game' after one move */
8660         if(*appData.finger) {
8661            static FILE *f;
8662            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8663            if(!f) f = fopen(appData.finger, "w");
8664            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8665            else { DisplayFatalError("Bad output file", errno, 0); return; }
8666            free(fen);
8667            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8668         }
8669
8670         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8671         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8672             int count = 0;
8673
8674             while( count < adjudicateLossPlies ) {
8675                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8676
8677                 if( count & 1 ) {
8678                     score = -score; /* Flip score for winning side */
8679                 }
8680
8681                 if( score > adjudicateLossThreshold ) {
8682                     break;
8683                 }
8684
8685                 count++;
8686             }
8687
8688             if( count >= adjudicateLossPlies ) {
8689                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8690
8691                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8692                     "Xboard adjudication",
8693                     GE_XBOARD );
8694
8695                 return;
8696             }
8697         }
8698
8699         if(Adjudicate(cps)) {
8700             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8701             return; // [HGM] adjudicate: for all automatic game ends
8702         }
8703
8704 #if ZIPPY
8705         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8706             first.initDone) {
8707           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8708                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8709                 SendToICS("draw ");
8710                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8711           }
8712           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8713           ics_user_moved = 1;
8714           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8715                 char buf[3*MSG_SIZ];
8716
8717                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8718                         programStats.score / 100.,
8719                         programStats.depth,
8720                         programStats.time / 100.,
8721                         (unsigned int)programStats.nodes,
8722                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8723                         programStats.movelist);
8724                 SendToICS(buf);
8725 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8726           }
8727         }
8728 #endif
8729
8730         /* [AS] Clear stats for next move */
8731         ClearProgramStats();
8732         thinkOutput[0] = NULLCHAR;
8733         hiddenThinkOutputState = 0;
8734
8735         bookHit = NULL;
8736         if (gameMode == TwoMachinesPlay) {
8737             /* [HGM] relaying draw offers moved to after reception of move */
8738             /* and interpreting offer as claim if it brings draw condition */
8739             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8740                 SendToProgram("draw\n", cps->other);
8741             }
8742             if (cps->other->sendTime) {
8743                 SendTimeRemaining(cps->other,
8744                                   cps->other->twoMachinesColor[0] == 'w');
8745             }
8746             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8747             if (firstMove && !bookHit) {
8748                 firstMove = FALSE;
8749                 if (cps->other->useColors) {
8750                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8751                 }
8752                 SendToProgram("go\n", cps->other);
8753             }
8754             cps->other->maybeThinking = TRUE;
8755         }
8756
8757         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8758
8759         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8760
8761         if (!pausing && appData.ringBellAfterMoves) {
8762             if(!roar) RingBell();
8763         }
8764
8765         /*
8766          * Reenable menu items that were disabled while
8767          * machine was thinking
8768          */
8769         if (gameMode != TwoMachinesPlay)
8770             SetUserThinkingEnables();
8771
8772         // [HGM] book: after book hit opponent has received move and is now in force mode
8773         // force the book reply into it, and then fake that it outputted this move by jumping
8774         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8775         if(bookHit) {
8776                 static char bookMove[MSG_SIZ]; // a bit generous?
8777
8778                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8779                 strcat(bookMove, bookHit);
8780                 message = bookMove;
8781                 cps = cps->other;
8782                 programStats.nodes = programStats.depth = programStats.time =
8783                 programStats.score = programStats.got_only_move = 0;
8784                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8785
8786                 if(cps->lastPing != cps->lastPong) {
8787                     savedMessage = message; // args for deferred call
8788                     savedState = cps;
8789                     ScheduleDelayedEvent(DeferredBookMove, 10);
8790                     return;
8791                 }
8792                 goto FakeBookMove;
8793         }
8794
8795         return;
8796     }
8797
8798     /* Set special modes for chess engines.  Later something general
8799      *  could be added here; for now there is just one kludge feature,
8800      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8801      *  when "xboard" is given as an interactive command.
8802      */
8803     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8804         cps->useSigint = FALSE;
8805         cps->useSigterm = FALSE;
8806     }
8807     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8808       ParseFeatures(message+8, cps);
8809       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8810     }
8811
8812     if (!strncmp(message, "setup ", 6) && 
8813         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8814           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8815                                         ) { // [HGM] allow first engine to define opening position
8816       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8817       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8818       *buf = NULLCHAR;
8819       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8820       if(startedFromSetupPosition) return;
8821       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8822       if(dummy >= 3) {
8823         while(message[s] && message[s++] != ' ');
8824         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8825            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8826             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8827             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8828           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8829           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8830         }
8831       }
8832       ParseFEN(boards[0], &dummy, message+s, FALSE);
8833       DrawPosition(TRUE, boards[0]);
8834       startedFromSetupPosition = TRUE;
8835       return;
8836     }
8837     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8838      * want this, I was asked to put it in, and obliged.
8839      */
8840     if (!strncmp(message, "setboard ", 9)) {
8841         Board initial_position;
8842
8843         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8844
8845         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8846             DisplayError(_("Bad FEN received from engine"), 0);
8847             return ;
8848         } else {
8849            Reset(TRUE, FALSE);
8850            CopyBoard(boards[0], initial_position);
8851            initialRulePlies = FENrulePlies;
8852            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8853            else gameMode = MachinePlaysBlack;
8854            DrawPosition(FALSE, boards[currentMove]);
8855         }
8856         return;
8857     }
8858
8859     /*
8860      * Look for communication commands
8861      */
8862     if (!strncmp(message, "telluser ", 9)) {
8863         if(message[9] == '\\' && message[10] == '\\')
8864             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8865         PlayTellSound();
8866         DisplayNote(message + 9);
8867         return;
8868     }
8869     if (!strncmp(message, "tellusererror ", 14)) {
8870         cps->userError = 1;
8871         if(message[14] == '\\' && message[15] == '\\')
8872             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8873         PlayTellSound();
8874         DisplayError(message + 14, 0);
8875         return;
8876     }
8877     if (!strncmp(message, "tellopponent ", 13)) {
8878       if (appData.icsActive) {
8879         if (loggedOn) {
8880           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8881           SendToICS(buf1);
8882         }
8883       } else {
8884         DisplayNote(message + 13);
8885       }
8886       return;
8887     }
8888     if (!strncmp(message, "tellothers ", 11)) {
8889       if (appData.icsActive) {
8890         if (loggedOn) {
8891           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8892           SendToICS(buf1);
8893         }
8894       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8895       return;
8896     }
8897     if (!strncmp(message, "tellall ", 8)) {
8898       if (appData.icsActive) {
8899         if (loggedOn) {
8900           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8901           SendToICS(buf1);
8902         }
8903       } else {
8904         DisplayNote(message + 8);
8905       }
8906       return;
8907     }
8908     if (strncmp(message, "warning", 7) == 0) {
8909         /* Undocumented feature, use tellusererror in new code */
8910         DisplayError(message, 0);
8911         return;
8912     }
8913     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8914         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8915         strcat(realname, " query");
8916         AskQuestion(realname, buf2, buf1, cps->pr);
8917         return;
8918     }
8919     /* Commands from the engine directly to ICS.  We don't allow these to be
8920      *  sent until we are logged on. Crafty kibitzes have been known to
8921      *  interfere with the login process.
8922      */
8923     if (loggedOn) {
8924         if (!strncmp(message, "tellics ", 8)) {
8925             SendToICS(message + 8);
8926             SendToICS("\n");
8927             return;
8928         }
8929         if (!strncmp(message, "tellicsnoalias ", 15)) {
8930             SendToICS(ics_prefix);
8931             SendToICS(message + 15);
8932             SendToICS("\n");
8933             return;
8934         }
8935         /* The following are for backward compatibility only */
8936         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8937             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8938             SendToICS(ics_prefix);
8939             SendToICS(message);
8940             SendToICS("\n");
8941             return;
8942         }
8943     }
8944     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8945         if(initPing == cps->lastPong) {
8946             if(gameInfo.variant == VariantUnknown) {
8947                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8948                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8949                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8950             }
8951             initPing = -1;
8952         }
8953         return;
8954     }
8955     if(!strncmp(message, "highlight ", 10)) {
8956         if(appData.testLegality && appData.markers) return;
8957         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8958         return;
8959     }
8960     if(!strncmp(message, "click ", 6)) {
8961         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8962         if(appData.testLegality || !appData.oneClick) return;
8963         sscanf(message+6, "%c%d%c", &f, &y, &c);
8964         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8965         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8966         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8967         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8968         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8969         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8970             LeftClick(Release, lastLeftX, lastLeftY);
8971         controlKey  = (c == ',');
8972         LeftClick(Press, x, y);
8973         LeftClick(Release, x, y);
8974         first.highlight = f;
8975         return;
8976     }
8977     /*
8978      * If the move is illegal, cancel it and redraw the board.
8979      * Also deal with other error cases.  Matching is rather loose
8980      * here to accommodate engines written before the spec.
8981      */
8982     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8983         strncmp(message, "Error", 5) == 0) {
8984         if (StrStr(message, "name") ||
8985             StrStr(message, "rating") || StrStr(message, "?") ||
8986             StrStr(message, "result") || StrStr(message, "board") ||
8987             StrStr(message, "bk") || StrStr(message, "computer") ||
8988             StrStr(message, "variant") || StrStr(message, "hint") ||
8989             StrStr(message, "random") || StrStr(message, "depth") ||
8990             StrStr(message, "accepted")) {
8991             return;
8992         }
8993         if (StrStr(message, "protover")) {
8994           /* Program is responding to input, so it's apparently done
8995              initializing, and this error message indicates it is
8996              protocol version 1.  So we don't need to wait any longer
8997              for it to initialize and send feature commands. */
8998           FeatureDone(cps, 1);
8999           cps->protocolVersion = 1;
9000           return;
9001         }
9002         cps->maybeThinking = FALSE;
9003
9004         if (StrStr(message, "draw")) {
9005             /* Program doesn't have "draw" command */
9006             cps->sendDrawOffers = 0;
9007             return;
9008         }
9009         if (cps->sendTime != 1 &&
9010             (StrStr(message, "time") || StrStr(message, "otim"))) {
9011           /* Program apparently doesn't have "time" or "otim" command */
9012           cps->sendTime = 0;
9013           return;
9014         }
9015         if (StrStr(message, "analyze")) {
9016             cps->analysisSupport = FALSE;
9017             cps->analyzing = FALSE;
9018 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9019             EditGameEvent(); // [HGM] try to preserve loaded game
9020             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9021             DisplayError(buf2, 0);
9022             return;
9023         }
9024         if (StrStr(message, "(no matching move)st")) {
9025           /* Special kludge for GNU Chess 4 only */
9026           cps->stKludge = TRUE;
9027           SendTimeControl(cps, movesPerSession, timeControl,
9028                           timeIncrement, appData.searchDepth,
9029                           searchTime);
9030           return;
9031         }
9032         if (StrStr(message, "(no matching move)sd")) {
9033           /* Special kludge for GNU Chess 4 only */
9034           cps->sdKludge = TRUE;
9035           SendTimeControl(cps, movesPerSession, timeControl,
9036                           timeIncrement, appData.searchDepth,
9037                           searchTime);
9038           return;
9039         }
9040         if (!StrStr(message, "llegal")) {
9041             return;
9042         }
9043         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9044             gameMode == IcsIdle) return;
9045         if (forwardMostMove <= backwardMostMove) return;
9046         if (pausing) PauseEvent();
9047       if(appData.forceIllegal) {
9048             // [HGM] illegal: machine refused move; force position after move into it
9049           SendToProgram("force\n", cps);
9050           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9051                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9052                 // when black is to move, while there might be nothing on a2 or black
9053                 // might already have the move. So send the board as if white has the move.
9054                 // But first we must change the stm of the engine, as it refused the last move
9055                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9056                 if(WhiteOnMove(forwardMostMove)) {
9057                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9058                     SendBoard(cps, forwardMostMove); // kludgeless board
9059                 } else {
9060                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9061                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9062                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9063                 }
9064           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9065             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9066                  gameMode == TwoMachinesPlay)
9067               SendToProgram("go\n", cps);
9068             return;
9069       } else
9070         if (gameMode == PlayFromGameFile) {
9071             /* Stop reading this game file */
9072             gameMode = EditGame;
9073             ModeHighlight();
9074         }
9075         /* [HGM] illegal-move claim should forfeit game when Xboard */
9076         /* only passes fully legal moves                            */
9077         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9078             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9079                                 "False illegal-move claim", GE_XBOARD );
9080             return; // do not take back move we tested as valid
9081         }
9082         currentMove = forwardMostMove-1;
9083         DisplayMove(currentMove-1); /* before DisplayMoveError */
9084         SwitchClocks(forwardMostMove-1); // [HGM] race
9085         DisplayBothClocks();
9086         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9087                 parseList[currentMove], _(cps->which));
9088         DisplayMoveError(buf1);
9089         DrawPosition(FALSE, boards[currentMove]);
9090
9091         SetUserThinkingEnables();
9092         return;
9093     }
9094     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9095         /* Program has a broken "time" command that
9096            outputs a string not ending in newline.
9097            Don't use it. */
9098         cps->sendTime = 0;
9099     }
9100
9101     /*
9102      * If chess program startup fails, exit with an error message.
9103      * Attempts to recover here are futile. [HGM] Well, we try anyway
9104      */
9105     if ((StrStr(message, "unknown host") != NULL)
9106         || (StrStr(message, "No remote directory") != NULL)
9107         || (StrStr(message, "not found") != NULL)
9108         || (StrStr(message, "No such file") != NULL)
9109         || (StrStr(message, "can't alloc") != NULL)
9110         || (StrStr(message, "Permission denied") != NULL)) {
9111
9112         cps->maybeThinking = FALSE;
9113         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9114                 _(cps->which), cps->program, cps->host, message);
9115         RemoveInputSource(cps->isr);
9116         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9117             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9118             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9119         }
9120         return;
9121     }
9122
9123     /*
9124      * Look for hint output
9125      */
9126     if (sscanf(message, "Hint: %s", buf1) == 1) {
9127         if (cps == &first && hintRequested) {
9128             hintRequested = FALSE;
9129             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9130                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9131                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9132                                     PosFlags(forwardMostMove),
9133                                     fromY, fromX, toY, toX, promoChar, buf1);
9134                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9135                 DisplayInformation(buf2);
9136             } else {
9137                 /* Hint move could not be parsed!? */
9138               snprintf(buf2, sizeof(buf2),
9139                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9140                         buf1, _(cps->which));
9141                 DisplayError(buf2, 0);
9142             }
9143         } else {
9144           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9145         }
9146         return;
9147     }
9148
9149     /*
9150      * Ignore other messages if game is not in progress
9151      */
9152     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9153         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9154
9155     /*
9156      * look for win, lose, draw, or draw offer
9157      */
9158     if (strncmp(message, "1-0", 3) == 0) {
9159         char *p, *q, *r = "";
9160         p = strchr(message, '{');
9161         if (p) {
9162             q = strchr(p, '}');
9163             if (q) {
9164                 *q = NULLCHAR;
9165                 r = p + 1;
9166             }
9167         }
9168         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9169         return;
9170     } else if (strncmp(message, "0-1", 3) == 0) {
9171         char *p, *q, *r = "";
9172         p = strchr(message, '{');
9173         if (p) {
9174             q = strchr(p, '}');
9175             if (q) {
9176                 *q = NULLCHAR;
9177                 r = p + 1;
9178             }
9179         }
9180         /* Kludge for Arasan 4.1 bug */
9181         if (strcmp(r, "Black resigns") == 0) {
9182             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9183             return;
9184         }
9185         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9186         return;
9187     } else if (strncmp(message, "1/2", 3) == 0) {
9188         char *p, *q, *r = "";
9189         p = strchr(message, '{');
9190         if (p) {
9191             q = strchr(p, '}');
9192             if (q) {
9193                 *q = NULLCHAR;
9194                 r = p + 1;
9195             }
9196         }
9197
9198         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9199         return;
9200
9201     } else if (strncmp(message, "White resign", 12) == 0) {
9202         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9203         return;
9204     } else if (strncmp(message, "Black resign", 12) == 0) {
9205         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9206         return;
9207     } else if (strncmp(message, "White matches", 13) == 0 ||
9208                strncmp(message, "Black matches", 13) == 0   ) {
9209         /* [HGM] ignore GNUShogi noises */
9210         return;
9211     } else if (strncmp(message, "White", 5) == 0 &&
9212                message[5] != '(' &&
9213                StrStr(message, "Black") == NULL) {
9214         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9215         return;
9216     } else if (strncmp(message, "Black", 5) == 0 &&
9217                message[5] != '(') {
9218         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9219         return;
9220     } else if (strcmp(message, "resign") == 0 ||
9221                strcmp(message, "computer resigns") == 0) {
9222         switch (gameMode) {
9223           case MachinePlaysBlack:
9224           case IcsPlayingBlack:
9225             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9226             break;
9227           case MachinePlaysWhite:
9228           case IcsPlayingWhite:
9229             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9230             break;
9231           case TwoMachinesPlay:
9232             if (cps->twoMachinesColor[0] == 'w')
9233               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9234             else
9235               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9236             break;
9237           default:
9238             /* can't happen */
9239             break;
9240         }
9241         return;
9242     } else if (strncmp(message, "opponent mates", 14) == 0) {
9243         switch (gameMode) {
9244           case MachinePlaysBlack:
9245           case IcsPlayingBlack:
9246             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9247             break;
9248           case MachinePlaysWhite:
9249           case IcsPlayingWhite:
9250             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9251             break;
9252           case TwoMachinesPlay:
9253             if (cps->twoMachinesColor[0] == 'w')
9254               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9255             else
9256               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9257             break;
9258           default:
9259             /* can't happen */
9260             break;
9261         }
9262         return;
9263     } else if (strncmp(message, "computer mates", 14) == 0) {
9264         switch (gameMode) {
9265           case MachinePlaysBlack:
9266           case IcsPlayingBlack:
9267             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9268             break;
9269           case MachinePlaysWhite:
9270           case IcsPlayingWhite:
9271             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9272             break;
9273           case TwoMachinesPlay:
9274             if (cps->twoMachinesColor[0] == 'w')
9275               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9276             else
9277               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9278             break;
9279           default:
9280             /* can't happen */
9281             break;
9282         }
9283         return;
9284     } else if (strncmp(message, "checkmate", 9) == 0) {
9285         if (WhiteOnMove(forwardMostMove)) {
9286             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9287         } else {
9288             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9289         }
9290         return;
9291     } else if (strstr(message, "Draw") != NULL ||
9292                strstr(message, "game is a draw") != NULL) {
9293         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9294         return;
9295     } else if (strstr(message, "offer") != NULL &&
9296                strstr(message, "draw") != NULL) {
9297 #if ZIPPY
9298         if (appData.zippyPlay && first.initDone) {
9299             /* Relay offer to ICS */
9300             SendToICS(ics_prefix);
9301             SendToICS("draw\n");
9302         }
9303 #endif
9304         cps->offeredDraw = 2; /* valid until this engine moves twice */
9305         if (gameMode == TwoMachinesPlay) {
9306             if (cps->other->offeredDraw) {
9307                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9308             /* [HGM] in two-machine mode we delay relaying draw offer      */
9309             /* until after we also have move, to see if it is really claim */
9310             }
9311         } else if (gameMode == MachinePlaysWhite ||
9312                    gameMode == MachinePlaysBlack) {
9313           if (userOfferedDraw) {
9314             DisplayInformation(_("Machine accepts your draw offer"));
9315             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9316           } else {
9317             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9318           }
9319         }
9320     }
9321
9322
9323     /*
9324      * Look for thinking output
9325      */
9326     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9327           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9328                                 ) {
9329         int plylev, mvleft, mvtot, curscore, time;
9330         char mvname[MOVE_LEN];
9331         u64 nodes; // [DM]
9332         char plyext;
9333         int ignore = FALSE;
9334         int prefixHint = FALSE;
9335         mvname[0] = NULLCHAR;
9336
9337         switch (gameMode) {
9338           case MachinePlaysBlack:
9339           case IcsPlayingBlack:
9340             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9341             break;
9342           case MachinePlaysWhite:
9343           case IcsPlayingWhite:
9344             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9345             break;
9346           case AnalyzeMode:
9347           case AnalyzeFile:
9348             break;
9349           case IcsObserving: /* [DM] icsEngineAnalyze */
9350             if (!appData.icsEngineAnalyze) ignore = TRUE;
9351             break;
9352           case TwoMachinesPlay:
9353             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9354                 ignore = TRUE;
9355             }
9356             break;
9357           default:
9358             ignore = TRUE;
9359             break;
9360         }
9361
9362         if (!ignore) {
9363             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9364             buf1[0] = NULLCHAR;
9365             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9366                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9367
9368                 if (plyext != ' ' && plyext != '\t') {
9369                     time *= 100;
9370                 }
9371
9372                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9373                 if( cps->scoreIsAbsolute &&
9374                     ( gameMode == MachinePlaysBlack ||
9375                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9376                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9377                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9378                      !WhiteOnMove(currentMove)
9379                     ) )
9380                 {
9381                     curscore = -curscore;
9382                 }
9383
9384                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9385
9386                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9387                         char buf[MSG_SIZ];
9388                         FILE *f;
9389                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9390                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9391                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9392                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9393                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9394                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9395                                 fclose(f);
9396                         }
9397                         else
9398                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9399                           DisplayError(_("failed writing PV"), 0);
9400                 }
9401
9402                 tempStats.depth = plylev;
9403                 tempStats.nodes = nodes;
9404                 tempStats.time = time;
9405                 tempStats.score = curscore;
9406                 tempStats.got_only_move = 0;
9407
9408                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9409                         int ticklen;
9410
9411                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9412                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9413                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9414                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9415                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9416                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9417                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9418                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9419                 }
9420
9421                 /* Buffer overflow protection */
9422                 if (pv[0] != NULLCHAR) {
9423                     if (strlen(pv) >= sizeof(tempStats.movelist)
9424                         && appData.debugMode) {
9425                         fprintf(debugFP,
9426                                 "PV is too long; using the first %u bytes.\n",
9427                                 (unsigned) sizeof(tempStats.movelist) - 1);
9428                     }
9429
9430                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9431                 } else {
9432                     sprintf(tempStats.movelist, " no PV\n");
9433                 }
9434
9435                 if (tempStats.seen_stat) {
9436                     tempStats.ok_to_send = 1;
9437                 }
9438
9439                 if (strchr(tempStats.movelist, '(') != NULL) {
9440                     tempStats.line_is_book = 1;
9441                     tempStats.nr_moves = 0;
9442                     tempStats.moves_left = 0;
9443                 } else {
9444                     tempStats.line_is_book = 0;
9445                 }
9446
9447                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9448                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9449
9450                 SendProgramStatsToFrontend( cps, &tempStats );
9451
9452                 /*
9453                     [AS] Protect the thinkOutput buffer from overflow... this
9454                     is only useful if buf1 hasn't overflowed first!
9455                 */
9456                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9457                          plylev,
9458                          (gameMode == TwoMachinesPlay ?
9459                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9460                          ((double) curscore) / 100.0,
9461                          prefixHint ? lastHint : "",
9462                          prefixHint ? " " : "" );
9463
9464                 if( buf1[0] != NULLCHAR ) {
9465                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9466
9467                     if( strlen(pv) > max_len ) {
9468                         if( appData.debugMode) {
9469                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9470                         }
9471                         pv[max_len+1] = '\0';
9472                     }
9473
9474                     strcat( thinkOutput, pv);
9475                 }
9476
9477                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9478                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9479                     DisplayMove(currentMove - 1);
9480                 }
9481                 return;
9482
9483             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9484                 /* crafty (9.25+) says "(only move) <move>"
9485                  * if there is only 1 legal move
9486                  */
9487                 sscanf(p, "(only move) %s", buf1);
9488                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9489                 sprintf(programStats.movelist, "%s (only move)", buf1);
9490                 programStats.depth = 1;
9491                 programStats.nr_moves = 1;
9492                 programStats.moves_left = 1;
9493                 programStats.nodes = 1;
9494                 programStats.time = 1;
9495                 programStats.got_only_move = 1;
9496
9497                 /* Not really, but we also use this member to
9498                    mean "line isn't going to change" (Crafty
9499                    isn't searching, so stats won't change) */
9500                 programStats.line_is_book = 1;
9501
9502                 SendProgramStatsToFrontend( cps, &programStats );
9503
9504                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9505                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9506                     DisplayMove(currentMove - 1);
9507                 }
9508                 return;
9509             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9510                               &time, &nodes, &plylev, &mvleft,
9511                               &mvtot, mvname) >= 5) {
9512                 /* The stat01: line is from Crafty (9.29+) in response
9513                    to the "." command */
9514                 programStats.seen_stat = 1;
9515                 cps->maybeThinking = TRUE;
9516
9517                 if (programStats.got_only_move || !appData.periodicUpdates)
9518                   return;
9519
9520                 programStats.depth = plylev;
9521                 programStats.time = time;
9522                 programStats.nodes = nodes;
9523                 programStats.moves_left = mvleft;
9524                 programStats.nr_moves = mvtot;
9525                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9526                 programStats.ok_to_send = 1;
9527                 programStats.movelist[0] = '\0';
9528
9529                 SendProgramStatsToFrontend( cps, &programStats );
9530
9531                 return;
9532
9533             } else if (strncmp(message,"++",2) == 0) {
9534                 /* Crafty 9.29+ outputs this */
9535                 programStats.got_fail = 2;
9536                 return;
9537
9538             } else if (strncmp(message,"--",2) == 0) {
9539                 /* Crafty 9.29+ outputs this */
9540                 programStats.got_fail = 1;
9541                 return;
9542
9543             } else if (thinkOutput[0] != NULLCHAR &&
9544                        strncmp(message, "    ", 4) == 0) {
9545                 unsigned message_len;
9546
9547                 p = message;
9548                 while (*p && *p == ' ') p++;
9549
9550                 message_len = strlen( p );
9551
9552                 /* [AS] Avoid buffer overflow */
9553                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9554                     strcat(thinkOutput, " ");
9555                     strcat(thinkOutput, p);
9556                 }
9557
9558                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9559                     strcat(programStats.movelist, " ");
9560                     strcat(programStats.movelist, p);
9561                 }
9562
9563                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9564                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9565                     DisplayMove(currentMove - 1);
9566                 }
9567                 return;
9568             }
9569         }
9570         else {
9571             buf1[0] = NULLCHAR;
9572
9573             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9574                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9575             {
9576                 ChessProgramStats cpstats;
9577
9578                 if (plyext != ' ' && plyext != '\t') {
9579                     time *= 100;
9580                 }
9581
9582                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9583                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9584                     curscore = -curscore;
9585                 }
9586
9587                 cpstats.depth = plylev;
9588                 cpstats.nodes = nodes;
9589                 cpstats.time = time;
9590                 cpstats.score = curscore;
9591                 cpstats.got_only_move = 0;
9592                 cpstats.movelist[0] = '\0';
9593
9594                 if (buf1[0] != NULLCHAR) {
9595                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9596                 }
9597
9598                 cpstats.ok_to_send = 0;
9599                 cpstats.line_is_book = 0;
9600                 cpstats.nr_moves = 0;
9601                 cpstats.moves_left = 0;
9602
9603                 SendProgramStatsToFrontend( cps, &cpstats );
9604             }
9605         }
9606     }
9607 }
9608
9609
9610 /* Parse a game score from the character string "game", and
9611    record it as the history of the current game.  The game
9612    score is NOT assumed to start from the standard position.
9613    The display is not updated in any way.
9614    */
9615 void
9616 ParseGameHistory (char *game)
9617 {
9618     ChessMove moveType;
9619     int fromX, fromY, toX, toY, boardIndex;
9620     char promoChar;
9621     char *p, *q;
9622     char buf[MSG_SIZ];
9623
9624     if (appData.debugMode)
9625       fprintf(debugFP, "Parsing game history: %s\n", game);
9626
9627     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9628     gameInfo.site = StrSave(appData.icsHost);
9629     gameInfo.date = PGNDate();
9630     gameInfo.round = StrSave("-");
9631
9632     /* Parse out names of players */
9633     while (*game == ' ') game++;
9634     p = buf;
9635     while (*game != ' ') *p++ = *game++;
9636     *p = NULLCHAR;
9637     gameInfo.white = StrSave(buf);
9638     while (*game == ' ') game++;
9639     p = buf;
9640     while (*game != ' ' && *game != '\n') *p++ = *game++;
9641     *p = NULLCHAR;
9642     gameInfo.black = StrSave(buf);
9643
9644     /* Parse moves */
9645     boardIndex = blackPlaysFirst ? 1 : 0;
9646     yynewstr(game);
9647     for (;;) {
9648         yyboardindex = boardIndex;
9649         moveType = (ChessMove) Myylex();
9650         switch (moveType) {
9651           case IllegalMove:             /* maybe suicide chess, etc. */
9652   if (appData.debugMode) {
9653     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9654     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9655     setbuf(debugFP, NULL);
9656   }
9657           case WhitePromotion:
9658           case BlackPromotion:
9659           case WhiteNonPromotion:
9660           case BlackNonPromotion:
9661           case NormalMove:
9662           case FirstLeg:
9663           case WhiteCapturesEnPassant:
9664           case BlackCapturesEnPassant:
9665           case WhiteKingSideCastle:
9666           case WhiteQueenSideCastle:
9667           case BlackKingSideCastle:
9668           case BlackQueenSideCastle:
9669           case WhiteKingSideCastleWild:
9670           case WhiteQueenSideCastleWild:
9671           case BlackKingSideCastleWild:
9672           case BlackQueenSideCastleWild:
9673           /* PUSH Fabien */
9674           case WhiteHSideCastleFR:
9675           case WhiteASideCastleFR:
9676           case BlackHSideCastleFR:
9677           case BlackASideCastleFR:
9678           /* POP Fabien */
9679             fromX = currentMoveString[0] - AAA;
9680             fromY = currentMoveString[1] - ONE;
9681             toX = currentMoveString[2] - AAA;
9682             toY = currentMoveString[3] - ONE;
9683             promoChar = currentMoveString[4];
9684             break;
9685           case WhiteDrop:
9686           case BlackDrop:
9687             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9688             fromX = moveType == WhiteDrop ?
9689               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9690             (int) CharToPiece(ToLower(currentMoveString[0]));
9691             fromY = DROP_RANK;
9692             toX = currentMoveString[2] - AAA;
9693             toY = currentMoveString[3] - ONE;
9694             promoChar = NULLCHAR;
9695             break;
9696           case AmbiguousMove:
9697             /* bug? */
9698             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9699   if (appData.debugMode) {
9700     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9701     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9702     setbuf(debugFP, NULL);
9703   }
9704             DisplayError(buf, 0);
9705             return;
9706           case ImpossibleMove:
9707             /* bug? */
9708             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9709   if (appData.debugMode) {
9710     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9711     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9712     setbuf(debugFP, NULL);
9713   }
9714             DisplayError(buf, 0);
9715             return;
9716           case EndOfFile:
9717             if (boardIndex < backwardMostMove) {
9718                 /* Oops, gap.  How did that happen? */
9719                 DisplayError(_("Gap in move list"), 0);
9720                 return;
9721             }
9722             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9723             if (boardIndex > forwardMostMove) {
9724                 forwardMostMove = boardIndex;
9725             }
9726             return;
9727           case ElapsedTime:
9728             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9729                 strcat(parseList[boardIndex-1], " ");
9730                 strcat(parseList[boardIndex-1], yy_text);
9731             }
9732             continue;
9733           case Comment:
9734           case PGNTag:
9735           case NAG:
9736           default:
9737             /* ignore */
9738             continue;
9739           case WhiteWins:
9740           case BlackWins:
9741           case GameIsDrawn:
9742           case GameUnfinished:
9743             if (gameMode == IcsExamining) {
9744                 if (boardIndex < backwardMostMove) {
9745                     /* Oops, gap.  How did that happen? */
9746                     return;
9747                 }
9748                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9749                 return;
9750             }
9751             gameInfo.result = moveType;
9752             p = strchr(yy_text, '{');
9753             if (p == NULL) p = strchr(yy_text, '(');
9754             if (p == NULL) {
9755                 p = yy_text;
9756                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9757             } else {
9758                 q = strchr(p, *p == '{' ? '}' : ')');
9759                 if (q != NULL) *q = NULLCHAR;
9760                 p++;
9761             }
9762             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9763             gameInfo.resultDetails = StrSave(p);
9764             continue;
9765         }
9766         if (boardIndex >= forwardMostMove &&
9767             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9768             backwardMostMove = blackPlaysFirst ? 1 : 0;
9769             return;
9770         }
9771         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9772                                  fromY, fromX, toY, toX, promoChar,
9773                                  parseList[boardIndex]);
9774         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9775         /* currentMoveString is set as a side-effect of yylex */
9776         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9777         strcat(moveList[boardIndex], "\n");
9778         boardIndex++;
9779         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9780         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9781           case MT_NONE:
9782           case MT_STALEMATE:
9783           default:
9784             break;
9785           case MT_CHECK:
9786             if(!IS_SHOGI(gameInfo.variant))
9787                 strcat(parseList[boardIndex - 1], "+");
9788             break;
9789           case MT_CHECKMATE:
9790           case MT_STAINMATE:
9791             strcat(parseList[boardIndex - 1], "#");
9792             break;
9793         }
9794     }
9795 }
9796
9797
9798 /* Apply a move to the given board  */
9799 void
9800 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9801 {
9802   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9803   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9804
9805     /* [HGM] compute & store e.p. status and castling rights for new position */
9806     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9807
9808       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9809       oldEP = (signed char)board[EP_STATUS];
9810       board[EP_STATUS] = EP_NONE;
9811
9812   if (fromY == DROP_RANK) {
9813         /* must be first */
9814         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9815             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9816             return;
9817         }
9818         piece = board[toY][toX] = (ChessSquare) fromX;
9819   } else {
9820       ChessSquare victim;
9821       int i;
9822
9823       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9824            victim = board[killY][killX],
9825            board[killY][killX] = EmptySquare,
9826            board[EP_STATUS] = EP_CAPTURE;
9827
9828       if( board[toY][toX] != EmptySquare ) {
9829            board[EP_STATUS] = EP_CAPTURE;
9830            if( (fromX != toX || fromY != toY) && // not igui!
9831                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9832                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9833                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9834            }
9835       }
9836
9837       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9838            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9839                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9840       } else
9841       if( board[fromY][fromX] == WhitePawn ) {
9842            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9843                board[EP_STATUS] = EP_PAWN_MOVE;
9844            if( toY-fromY==2) {
9845                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9846                         gameInfo.variant != VariantBerolina || toX < fromX)
9847                       board[EP_STATUS] = toX | berolina;
9848                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9849                         gameInfo.variant != VariantBerolina || toX > fromX)
9850                       board[EP_STATUS] = toX;
9851            }
9852       } else
9853       if( board[fromY][fromX] == BlackPawn ) {
9854            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9855                board[EP_STATUS] = EP_PAWN_MOVE;
9856            if( toY-fromY== -2) {
9857                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9858                         gameInfo.variant != VariantBerolina || toX < fromX)
9859                       board[EP_STATUS] = toX | berolina;
9860                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9861                         gameInfo.variant != VariantBerolina || toX > fromX)
9862                       board[EP_STATUS] = toX;
9863            }
9864        }
9865
9866        for(i=0; i<nrCastlingRights; i++) {
9867            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9868               board[CASTLING][i] == toX   && castlingRank[i] == toY
9869              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9870        }
9871
9872        if(gameInfo.variant == VariantSChess) { // update virginity
9873            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9874            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9875            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9876            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9877        }
9878
9879      if (fromX == toX && fromY == toY) return;
9880
9881      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9882      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9883      if(gameInfo.variant == VariantKnightmate)
9884          king += (int) WhiteUnicorn - (int) WhiteKing;
9885
9886     /* Code added by Tord: */
9887     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9888     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9889         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9890       board[fromY][fromX] = EmptySquare;
9891       board[toY][toX] = EmptySquare;
9892       if((toX > fromX) != (piece == WhiteRook)) {
9893         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9894       } else {
9895         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9896       }
9897     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9898                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9899       board[fromY][fromX] = EmptySquare;
9900       board[toY][toX] = EmptySquare;
9901       if((toX > fromX) != (piece == BlackRook)) {
9902         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9903       } else {
9904         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9905       }
9906     /* End of code added by Tord */
9907
9908     } else if (board[fromY][fromX] == king
9909         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9910         && toY == fromY && toX > fromX+1) {
9911         board[fromY][fromX] = EmptySquare;
9912         board[toY][toX] = king;
9913         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9914         board[fromY][BOARD_RGHT-1] = EmptySquare;
9915     } else if (board[fromY][fromX] == king
9916         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9917                && toY == fromY && toX < fromX-1) {
9918         board[fromY][fromX] = EmptySquare;
9919         board[toY][toX] = king;
9920         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9921         board[fromY][BOARD_LEFT] = EmptySquare;
9922     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9923                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9924                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9925                ) {
9926         /* white pawn promotion */
9927         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9928         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9929             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9930         board[fromY][fromX] = EmptySquare;
9931     } else if ((fromY >= BOARD_HEIGHT>>1)
9932                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9933                && (toX != fromX)
9934                && gameInfo.variant != VariantXiangqi
9935                && gameInfo.variant != VariantBerolina
9936                && (board[fromY][fromX] == WhitePawn)
9937                && (board[toY][toX] == EmptySquare)) {
9938         board[fromY][fromX] = EmptySquare;
9939         board[toY][toX] = WhitePawn;
9940         captured = board[toY - 1][toX];
9941         board[toY - 1][toX] = EmptySquare;
9942     } else if ((fromY == BOARD_HEIGHT-4)
9943                && (toX == fromX)
9944                && gameInfo.variant == VariantBerolina
9945                && (board[fromY][fromX] == WhitePawn)
9946                && (board[toY][toX] == EmptySquare)) {
9947         board[fromY][fromX] = EmptySquare;
9948         board[toY][toX] = WhitePawn;
9949         if(oldEP & EP_BEROLIN_A) {
9950                 captured = board[fromY][fromX-1];
9951                 board[fromY][fromX-1] = EmptySquare;
9952         }else{  captured = board[fromY][fromX+1];
9953                 board[fromY][fromX+1] = EmptySquare;
9954         }
9955     } else if (board[fromY][fromX] == king
9956         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9957                && toY == fromY && toX > fromX+1) {
9958         board[fromY][fromX] = EmptySquare;
9959         board[toY][toX] = king;
9960         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9961         board[fromY][BOARD_RGHT-1] = EmptySquare;
9962     } else if (board[fromY][fromX] == king
9963         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9964                && toY == fromY && toX < fromX-1) {
9965         board[fromY][fromX] = EmptySquare;
9966         board[toY][toX] = king;
9967         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9968         board[fromY][BOARD_LEFT] = EmptySquare;
9969     } else if (fromY == 7 && fromX == 3
9970                && board[fromY][fromX] == BlackKing
9971                && toY == 7 && toX == 5) {
9972         board[fromY][fromX] = EmptySquare;
9973         board[toY][toX] = BlackKing;
9974         board[fromY][7] = EmptySquare;
9975         board[toY][4] = BlackRook;
9976     } else if (fromY == 7 && fromX == 3
9977                && board[fromY][fromX] == BlackKing
9978                && toY == 7 && toX == 1) {
9979         board[fromY][fromX] = EmptySquare;
9980         board[toY][toX] = BlackKing;
9981         board[fromY][0] = EmptySquare;
9982         board[toY][2] = BlackRook;
9983     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9984                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9985                && toY < promoRank && promoChar
9986                ) {
9987         /* black pawn promotion */
9988         board[toY][toX] = CharToPiece(ToLower(promoChar));
9989         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9990             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9991         board[fromY][fromX] = EmptySquare;
9992     } else if ((fromY < BOARD_HEIGHT>>1)
9993                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9994                && (toX != fromX)
9995                && gameInfo.variant != VariantXiangqi
9996                && gameInfo.variant != VariantBerolina
9997                && (board[fromY][fromX] == BlackPawn)
9998                && (board[toY][toX] == EmptySquare)) {
9999         board[fromY][fromX] = EmptySquare;
10000         board[toY][toX] = BlackPawn;
10001         captured = board[toY + 1][toX];
10002         board[toY + 1][toX] = EmptySquare;
10003     } else if ((fromY == 3)
10004                && (toX == fromX)
10005                && gameInfo.variant == VariantBerolina
10006                && (board[fromY][fromX] == BlackPawn)
10007                && (board[toY][toX] == EmptySquare)) {
10008         board[fromY][fromX] = EmptySquare;
10009         board[toY][toX] = BlackPawn;
10010         if(oldEP & EP_BEROLIN_A) {
10011                 captured = board[fromY][fromX-1];
10012                 board[fromY][fromX-1] = EmptySquare;
10013         }else{  captured = board[fromY][fromX+1];
10014                 board[fromY][fromX+1] = EmptySquare;
10015         }
10016     } else {
10017         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10018         board[fromY][fromX] = EmptySquare;
10019         board[toY][toX] = piece;
10020     }
10021   }
10022
10023     if (gameInfo.holdingsWidth != 0) {
10024
10025       /* !!A lot more code needs to be written to support holdings  */
10026       /* [HGM] OK, so I have written it. Holdings are stored in the */
10027       /* penultimate board files, so they are automaticlly stored   */
10028       /* in the game history.                                       */
10029       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10030                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10031         /* Delete from holdings, by decreasing count */
10032         /* and erasing image if necessary            */
10033         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10034         if(p < (int) BlackPawn) { /* white drop */
10035              p -= (int)WhitePawn;
10036                  p = PieceToNumber((ChessSquare)p);
10037              if(p >= gameInfo.holdingsSize) p = 0;
10038              if(--board[p][BOARD_WIDTH-2] <= 0)
10039                   board[p][BOARD_WIDTH-1] = EmptySquare;
10040              if((int)board[p][BOARD_WIDTH-2] < 0)
10041                         board[p][BOARD_WIDTH-2] = 0;
10042         } else {                  /* black drop */
10043              p -= (int)BlackPawn;
10044                  p = PieceToNumber((ChessSquare)p);
10045              if(p >= gameInfo.holdingsSize) p = 0;
10046              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10047                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10048              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10049                         board[BOARD_HEIGHT-1-p][1] = 0;
10050         }
10051       }
10052       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10053           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10054         /* [HGM] holdings: Add to holdings, if holdings exist */
10055         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10056                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10057                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10058         }
10059         p = (int) captured;
10060         if (p >= (int) BlackPawn) {
10061           p -= (int)BlackPawn;
10062           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10063                   /* in Shogi restore piece to its original  first */
10064                   captured = (ChessSquare) (DEMOTED captured);
10065                   p = DEMOTED p;
10066           }
10067           p = PieceToNumber((ChessSquare)p);
10068           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10069           board[p][BOARD_WIDTH-2]++;
10070           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10071         } else {
10072           p -= (int)WhitePawn;
10073           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10074                   captured = (ChessSquare) (DEMOTED captured);
10075                   p = DEMOTED p;
10076           }
10077           p = PieceToNumber((ChessSquare)p);
10078           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10079           board[BOARD_HEIGHT-1-p][1]++;
10080           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10081         }
10082       }
10083     } else if (gameInfo.variant == VariantAtomic) {
10084       if (captured != EmptySquare) {
10085         int y, x;
10086         for (y = toY-1; y <= toY+1; y++) {
10087           for (x = toX-1; x <= toX+1; x++) {
10088             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10089                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10090               board[y][x] = EmptySquare;
10091             }
10092           }
10093         }
10094         board[toY][toX] = EmptySquare;
10095       }
10096     }
10097
10098     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10099         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10100     } else
10101     if(promoChar == '+') {
10102         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10103         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10104         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10105           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10106     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10107         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10108         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10109            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10110         board[toY][toX] = newPiece;
10111     }
10112     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10113                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10114         // [HGM] superchess: take promotion piece out of holdings
10115         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10116         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10117             if(!--board[k][BOARD_WIDTH-2])
10118                 board[k][BOARD_WIDTH-1] = EmptySquare;
10119         } else {
10120             if(!--board[BOARD_HEIGHT-1-k][1])
10121                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10122         }
10123     }
10124 }
10125
10126 /* Updates forwardMostMove */
10127 void
10128 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10129 {
10130     int x = toX, y = toY;
10131     char *s = parseList[forwardMostMove];
10132     ChessSquare p = boards[forwardMostMove][toY][toX];
10133 //    forwardMostMove++; // [HGM] bare: moved downstream
10134
10135     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10136     (void) CoordsToAlgebraic(boards[forwardMostMove],
10137                              PosFlags(forwardMostMove),
10138                              fromY, fromX, y, x, promoChar,
10139                              s);
10140     if(killX >= 0 && killY >= 0)
10141         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10142
10143     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10144         int timeLeft; static int lastLoadFlag=0; int king, piece;
10145         piece = boards[forwardMostMove][fromY][fromX];
10146         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10147         if(gameInfo.variant == VariantKnightmate)
10148             king += (int) WhiteUnicorn - (int) WhiteKing;
10149         if(forwardMostMove == 0) {
10150             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10151                 fprintf(serverMoves, "%s;", UserName());
10152             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10153                 fprintf(serverMoves, "%s;", second.tidy);
10154             fprintf(serverMoves, "%s;", first.tidy);
10155             if(gameMode == MachinePlaysWhite)
10156                 fprintf(serverMoves, "%s;", UserName());
10157             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10158                 fprintf(serverMoves, "%s;", second.tidy);
10159         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10160         lastLoadFlag = loadFlag;
10161         // print base move
10162         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10163         // print castling suffix
10164         if( toY == fromY && piece == king ) {
10165             if(toX-fromX > 1)
10166                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10167             if(fromX-toX >1)
10168                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10169         }
10170         // e.p. suffix
10171         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10172              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10173              boards[forwardMostMove][toY][toX] == EmptySquare
10174              && fromX != toX && fromY != toY)
10175                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10176         // promotion suffix
10177         if(promoChar != NULLCHAR) {
10178             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10179                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10180                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10181             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10182         }
10183         if(!loadFlag) {
10184                 char buf[MOVE_LEN*2], *p; int len;
10185             fprintf(serverMoves, "/%d/%d",
10186                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10187             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10188             else                      timeLeft = blackTimeRemaining/1000;
10189             fprintf(serverMoves, "/%d", timeLeft);
10190                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10191                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10192                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10193                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10194             fprintf(serverMoves, "/%s", buf);
10195         }
10196         fflush(serverMoves);
10197     }
10198
10199     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10200         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10201       return;
10202     }
10203     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10204     if (commentList[forwardMostMove+1] != NULL) {
10205         free(commentList[forwardMostMove+1]);
10206         commentList[forwardMostMove+1] = NULL;
10207     }
10208     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10209     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10210     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10211     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10212     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10213     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10214     adjustedClock = FALSE;
10215     gameInfo.result = GameUnfinished;
10216     if (gameInfo.resultDetails != NULL) {
10217         free(gameInfo.resultDetails);
10218         gameInfo.resultDetails = NULL;
10219     }
10220     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10221                               moveList[forwardMostMove - 1]);
10222     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10223       case MT_NONE:
10224       case MT_STALEMATE:
10225       default:
10226         break;
10227       case MT_CHECK:
10228         if(!IS_SHOGI(gameInfo.variant))
10229             strcat(parseList[forwardMostMove - 1], "+");
10230         break;
10231       case MT_CHECKMATE:
10232       case MT_STAINMATE:
10233         strcat(parseList[forwardMostMove - 1], "#");
10234         break;
10235     }
10236 }
10237
10238 /* Updates currentMove if not pausing */
10239 void
10240 ShowMove (int fromX, int fromY, int toX, int toY)
10241 {
10242     int instant = (gameMode == PlayFromGameFile) ?
10243         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10244     if(appData.noGUI) return;
10245     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10246         if (!instant) {
10247             if (forwardMostMove == currentMove + 1) {
10248                 AnimateMove(boards[forwardMostMove - 1],
10249                             fromX, fromY, toX, toY);
10250             }
10251         }
10252         currentMove = forwardMostMove;
10253     }
10254
10255     killX = killY = -1; // [HGM] lion: used up
10256
10257     if (instant) return;
10258
10259     DisplayMove(currentMove - 1);
10260     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10261             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10262                 SetHighlights(fromX, fromY, toX, toY);
10263             }
10264     }
10265     DrawPosition(FALSE, boards[currentMove]);
10266     DisplayBothClocks();
10267     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10268 }
10269
10270 void
10271 SendEgtPath (ChessProgramState *cps)
10272 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10273         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10274
10275         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10276
10277         while(*p) {
10278             char c, *q = name+1, *r, *s;
10279
10280             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10281             while(*p && *p != ',') *q++ = *p++;
10282             *q++ = ':'; *q = 0;
10283             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10284                 strcmp(name, ",nalimov:") == 0 ) {
10285                 // take nalimov path from the menu-changeable option first, if it is defined
10286               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10287                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10288             } else
10289             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10290                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10291                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10292                 s = r = StrStr(s, ":") + 1; // beginning of path info
10293                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10294                 c = *r; *r = 0;             // temporarily null-terminate path info
10295                     *--q = 0;               // strip of trailig ':' from name
10296                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10297                 *r = c;
10298                 SendToProgram(buf,cps);     // send egtbpath command for this format
10299             }
10300             if(*p == ',') p++; // read away comma to position for next format name
10301         }
10302 }
10303
10304 static int
10305 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10306 {
10307       int width = 8, height = 8, holdings = 0;             // most common sizes
10308       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10309       // correct the deviations default for each variant
10310       if( v == VariantXiangqi ) width = 9,  height = 10;
10311       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10312       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10313       if( v == VariantCapablanca || v == VariantCapaRandom ||
10314           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10315                                 width = 10;
10316       if( v == VariantCourier ) width = 12;
10317       if( v == VariantSuper )                            holdings = 8;
10318       if( v == VariantGreat )   width = 10,              holdings = 8;
10319       if( v == VariantSChess )                           holdings = 7;
10320       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10321       if( v == VariantChuChess) width = 10, height = 10;
10322       if( v == VariantChu )     width = 12, height = 12;
10323       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10324              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10325              holdingsSize >= 0 && holdingsSize != holdings;
10326 }
10327
10328 char variantError[MSG_SIZ];
10329
10330 char *
10331 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10332 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10333       char *p, *variant = VariantName(v);
10334       static char b[MSG_SIZ];
10335       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10336            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10337                                                holdingsSize, variant); // cook up sized variant name
10338            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10339            if(StrStr(list, b) == NULL) {
10340                // specific sized variant not known, check if general sizing allowed
10341                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10342                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10343                             boardWidth, boardHeight, holdingsSize, engine);
10344                    return NULL;
10345                }
10346                /* [HGM] here we really should compare with the maximum supported board size */
10347            }
10348       } else snprintf(b, MSG_SIZ,"%s", variant);
10349       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10350       p = StrStr(list, b);
10351       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10352       if(p == NULL) {
10353           // occurs not at all in list, or only as sub-string
10354           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10355           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10356               int l = strlen(variantError);
10357               char *q;
10358               while(p != list && p[-1] != ',') p--;
10359               q = strchr(p, ',');
10360               if(q) *q = NULLCHAR;
10361               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10362               if(q) *q= ',';
10363           }
10364           return NULL;
10365       }
10366       return b;
10367 }
10368
10369 void
10370 InitChessProgram (ChessProgramState *cps, int setup)
10371 /* setup needed to setup FRC opening position */
10372 {
10373     char buf[MSG_SIZ], *b;
10374     if (appData.noChessProgram) return;
10375     hintRequested = FALSE;
10376     bookRequested = FALSE;
10377
10378     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10379     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10380     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10381     if(cps->memSize) { /* [HGM] memory */
10382       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10383         SendToProgram(buf, cps);
10384     }
10385     SendEgtPath(cps); /* [HGM] EGT */
10386     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10387       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10388         SendToProgram(buf, cps);
10389     }
10390
10391     SendToProgram(cps->initString, cps);
10392     if (gameInfo.variant != VariantNormal &&
10393         gameInfo.variant != VariantLoadable
10394         /* [HGM] also send variant if board size non-standard */
10395         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10396
10397       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10398                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10399       if (b == NULL) {
10400         DisplayFatalError(variantError, 0, 1);
10401         return;
10402       }
10403
10404       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10405       SendToProgram(buf, cps);
10406     }
10407     currentlyInitializedVariant = gameInfo.variant;
10408
10409     /* [HGM] send opening position in FRC to first engine */
10410     if(setup) {
10411           SendToProgram("force\n", cps);
10412           SendBoard(cps, 0);
10413           /* engine is now in force mode! Set flag to wake it up after first move. */
10414           setboardSpoiledMachineBlack = 1;
10415     }
10416
10417     if (cps->sendICS) {
10418       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10419       SendToProgram(buf, cps);
10420     }
10421     cps->maybeThinking = FALSE;
10422     cps->offeredDraw = 0;
10423     if (!appData.icsActive) {
10424         SendTimeControl(cps, movesPerSession, timeControl,
10425                         timeIncrement, appData.searchDepth,
10426                         searchTime);
10427     }
10428     if (appData.showThinking
10429         // [HGM] thinking: four options require thinking output to be sent
10430         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10431                                 ) {
10432         SendToProgram("post\n", cps);
10433     }
10434     SendToProgram("hard\n", cps);
10435     if (!appData.ponderNextMove) {
10436         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10437            it without being sure what state we are in first.  "hard"
10438            is not a toggle, so that one is OK.
10439          */
10440         SendToProgram("easy\n", cps);
10441     }
10442     if (cps->usePing) {
10443       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10444       SendToProgram(buf, cps);
10445     }
10446     cps->initDone = TRUE;
10447     ClearEngineOutputPane(cps == &second);
10448 }
10449
10450
10451 void
10452 ResendOptions (ChessProgramState *cps)
10453 { // send the stored value of the options
10454   int i;
10455   char buf[MSG_SIZ];
10456   Option *opt = cps->option;
10457   for(i=0; i<cps->nrOptions; i++, opt++) {
10458       switch(opt->type) {
10459         case Spin:
10460         case Slider:
10461         case CheckBox:
10462             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10463           break;
10464         case ComboBox:
10465           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10466           break;
10467         default:
10468             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10469           break;
10470         case Button:
10471         case SaveButton:
10472           continue;
10473       }
10474       SendToProgram(buf, cps);
10475   }
10476 }
10477
10478 void
10479 StartChessProgram (ChessProgramState *cps)
10480 {
10481     char buf[MSG_SIZ];
10482     int err;
10483
10484     if (appData.noChessProgram) return;
10485     cps->initDone = FALSE;
10486
10487     if (strcmp(cps->host, "localhost") == 0) {
10488         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10489     } else if (*appData.remoteShell == NULLCHAR) {
10490         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10491     } else {
10492         if (*appData.remoteUser == NULLCHAR) {
10493           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10494                     cps->program);
10495         } else {
10496           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10497                     cps->host, appData.remoteUser, cps->program);
10498         }
10499         err = StartChildProcess(buf, "", &cps->pr);
10500     }
10501
10502     if (err != 0) {
10503       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10504         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10505         if(cps != &first) return;
10506         appData.noChessProgram = TRUE;
10507         ThawUI();
10508         SetNCPMode();
10509 //      DisplayFatalError(buf, err, 1);
10510 //      cps->pr = NoProc;
10511 //      cps->isr = NULL;
10512         return;
10513     }
10514
10515     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10516     if (cps->protocolVersion > 1) {
10517       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10518       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10519         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10520         cps->comboCnt = 0;  //                and values of combo boxes
10521       }
10522       SendToProgram(buf, cps);
10523       if(cps->reload) ResendOptions(cps);
10524     } else {
10525       SendToProgram("xboard\n", cps);
10526     }
10527 }
10528
10529 void
10530 TwoMachinesEventIfReady P((void))
10531 {
10532   static int curMess = 0;
10533   if (first.lastPing != first.lastPong) {
10534     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10535     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10536     return;
10537   }
10538   if (second.lastPing != second.lastPong) {
10539     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10540     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10541     return;
10542   }
10543   DisplayMessage("", ""); curMess = 0;
10544   TwoMachinesEvent();
10545 }
10546
10547 char *
10548 MakeName (char *template)
10549 {
10550     time_t clock;
10551     struct tm *tm;
10552     static char buf[MSG_SIZ];
10553     char *p = buf;
10554     int i;
10555
10556     clock = time((time_t *)NULL);
10557     tm = localtime(&clock);
10558
10559     while(*p++ = *template++) if(p[-1] == '%') {
10560         switch(*template++) {
10561           case 0:   *p = 0; return buf;
10562           case 'Y': i = tm->tm_year+1900; break;
10563           case 'y': i = tm->tm_year-100; break;
10564           case 'M': i = tm->tm_mon+1; break;
10565           case 'd': i = tm->tm_mday; break;
10566           case 'h': i = tm->tm_hour; break;
10567           case 'm': i = tm->tm_min; break;
10568           case 's': i = tm->tm_sec; break;
10569           default:  i = 0;
10570         }
10571         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10572     }
10573     return buf;
10574 }
10575
10576 int
10577 CountPlayers (char *p)
10578 {
10579     int n = 0;
10580     while(p = strchr(p, '\n')) p++, n++; // count participants
10581     return n;
10582 }
10583
10584 FILE *
10585 WriteTourneyFile (char *results, FILE *f)
10586 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10587     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10588     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10589         // create a file with tournament description
10590         fprintf(f, "-participants {%s}\n", appData.participants);
10591         fprintf(f, "-seedBase %d\n", appData.seedBase);
10592         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10593         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10594         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10595         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10596         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10597         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10598         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10599         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10600         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10601         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10602         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10603         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10604         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10605         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10606         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10607         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10608         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10609         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10610         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10611         fprintf(f, "-smpCores %d\n", appData.smpCores);
10612         if(searchTime > 0)
10613                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10614         else {
10615                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10616                 fprintf(f, "-tc %s\n", appData.timeControl);
10617                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10618         }
10619         fprintf(f, "-results \"%s\"\n", results);
10620     }
10621     return f;
10622 }
10623
10624 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10625
10626 void
10627 Substitute (char *participants, int expunge)
10628 {
10629     int i, changed, changes=0, nPlayers=0;
10630     char *p, *q, *r, buf[MSG_SIZ];
10631     if(participants == NULL) return;
10632     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10633     r = p = participants; q = appData.participants;
10634     while(*p && *p == *q) {
10635         if(*p == '\n') r = p+1, nPlayers++;
10636         p++; q++;
10637     }
10638     if(*p) { // difference
10639         while(*p && *p++ != '\n');
10640         while(*q && *q++ != '\n');
10641       changed = nPlayers;
10642         changes = 1 + (strcmp(p, q) != 0);
10643     }
10644     if(changes == 1) { // a single engine mnemonic was changed
10645         q = r; while(*q) nPlayers += (*q++ == '\n');
10646         p = buf; while(*r && (*p = *r++) != '\n') p++;
10647         *p = NULLCHAR;
10648         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10649         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10650         if(mnemonic[i]) { // The substitute is valid
10651             FILE *f;
10652             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10653                 flock(fileno(f), LOCK_EX);
10654                 ParseArgsFromFile(f);
10655                 fseek(f, 0, SEEK_SET);
10656                 FREE(appData.participants); appData.participants = participants;
10657                 if(expunge) { // erase results of replaced engine
10658                     int len = strlen(appData.results), w, b, dummy;
10659                     for(i=0; i<len; i++) {
10660                         Pairing(i, nPlayers, &w, &b, &dummy);
10661                         if((w == changed || b == changed) && appData.results[i] == '*') {
10662                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10663                             fclose(f);
10664                             return;
10665                         }
10666                     }
10667                     for(i=0; i<len; i++) {
10668                         Pairing(i, nPlayers, &w, &b, &dummy);
10669                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10670                     }
10671                 }
10672                 WriteTourneyFile(appData.results, f);
10673                 fclose(f); // release lock
10674                 return;
10675             }
10676         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10677     }
10678     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10679     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10680     free(participants);
10681     return;
10682 }
10683
10684 int
10685 CheckPlayers (char *participants)
10686 {
10687         int i;
10688         char buf[MSG_SIZ], *p;
10689         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10690         while(p = strchr(participants, '\n')) {
10691             *p = NULLCHAR;
10692             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10693             if(!mnemonic[i]) {
10694                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10695                 *p = '\n';
10696                 DisplayError(buf, 0);
10697                 return 1;
10698             }
10699             *p = '\n';
10700             participants = p + 1;
10701         }
10702         return 0;
10703 }
10704
10705 int
10706 CreateTourney (char *name)
10707 {
10708         FILE *f;
10709         if(matchMode && strcmp(name, appData.tourneyFile)) {
10710              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10711         }
10712         if(name[0] == NULLCHAR) {
10713             if(appData.participants[0])
10714                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10715             return 0;
10716         }
10717         f = fopen(name, "r");
10718         if(f) { // file exists
10719             ASSIGN(appData.tourneyFile, name);
10720             ParseArgsFromFile(f); // parse it
10721         } else {
10722             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10723             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10724                 DisplayError(_("Not enough participants"), 0);
10725                 return 0;
10726             }
10727             if(CheckPlayers(appData.participants)) return 0;
10728             ASSIGN(appData.tourneyFile, name);
10729             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10730             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10731         }
10732         fclose(f);
10733         appData.noChessProgram = FALSE;
10734         appData.clockMode = TRUE;
10735         SetGNUMode();
10736         return 1;
10737 }
10738
10739 int
10740 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10741 {
10742     char buf[MSG_SIZ], *p, *q;
10743     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10744     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10745     skip = !all && group[0]; // if group requested, we start in skip mode
10746     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10747         p = names; q = buf; header = 0;
10748         while(*p && *p != '\n') *q++ = *p++;
10749         *q = 0;
10750         if(*p == '\n') p++;
10751         if(buf[0] == '#') {
10752             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10753             depth++; // we must be entering a new group
10754             if(all) continue; // suppress printing group headers when complete list requested
10755             header = 1;
10756             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10757         }
10758         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10759         if(engineList[i]) free(engineList[i]);
10760         engineList[i] = strdup(buf);
10761         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10762         if(engineMnemonic[i]) free(engineMnemonic[i]);
10763         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10764             strcat(buf, " (");
10765             sscanf(q + 8, "%s", buf + strlen(buf));
10766             strcat(buf, ")");
10767         }
10768         engineMnemonic[i] = strdup(buf);
10769         i++;
10770     }
10771     engineList[i] = engineMnemonic[i] = NULL;
10772     return i;
10773 }
10774
10775 // following implemented as macro to avoid type limitations
10776 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10777
10778 void
10779 SwapEngines (int n)
10780 {   // swap settings for first engine and other engine (so far only some selected options)
10781     int h;
10782     char *p;
10783     if(n == 0) return;
10784     SWAP(directory, p)
10785     SWAP(chessProgram, p)
10786     SWAP(isUCI, h)
10787     SWAP(hasOwnBookUCI, h)
10788     SWAP(protocolVersion, h)
10789     SWAP(reuse, h)
10790     SWAP(scoreIsAbsolute, h)
10791     SWAP(timeOdds, h)
10792     SWAP(logo, p)
10793     SWAP(pgnName, p)
10794     SWAP(pvSAN, h)
10795     SWAP(engOptions, p)
10796     SWAP(engInitString, p)
10797     SWAP(computerString, p)
10798     SWAP(features, p)
10799     SWAP(fenOverride, p)
10800     SWAP(NPS, h)
10801     SWAP(accumulateTC, h)
10802     SWAP(drawDepth, h)
10803     SWAP(host, p)
10804 }
10805
10806 int
10807 GetEngineLine (char *s, int n)
10808 {
10809     int i;
10810     char buf[MSG_SIZ];
10811     extern char *icsNames;
10812     if(!s || !*s) return 0;
10813     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10814     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10815     if(!mnemonic[i]) return 0;
10816     if(n == 11) return 1; // just testing if there was a match
10817     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10818     if(n == 1) SwapEngines(n);
10819     ParseArgsFromString(buf);
10820     if(n == 1) SwapEngines(n);
10821     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10822         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10823         ParseArgsFromString(buf);
10824     }
10825     return 1;
10826 }
10827
10828 int
10829 SetPlayer (int player, char *p)
10830 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10831     int i;
10832     char buf[MSG_SIZ], *engineName;
10833     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10834     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10835     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10836     if(mnemonic[i]) {
10837         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10838         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10839         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10840         ParseArgsFromString(buf);
10841     } else { // no engine with this nickname is installed!
10842         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10843         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10844         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10845         ModeHighlight();
10846         DisplayError(buf, 0);
10847         return 0;
10848     }
10849     free(engineName);
10850     return i;
10851 }
10852
10853 char *recentEngines;
10854
10855 void
10856 RecentEngineEvent (int nr)
10857 {
10858     int n;
10859 //    SwapEngines(1); // bump first to second
10860 //    ReplaceEngine(&second, 1); // and load it there
10861     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10862     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10863     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10864         ReplaceEngine(&first, 0);
10865         FloatToFront(&appData.recentEngineList, command[n]);
10866     }
10867 }
10868
10869 int
10870 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10871 {   // determine players from game number
10872     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10873
10874     if(appData.tourneyType == 0) {
10875         roundsPerCycle = (nPlayers - 1) | 1;
10876         pairingsPerRound = nPlayers / 2;
10877     } else if(appData.tourneyType > 0) {
10878         roundsPerCycle = nPlayers - appData.tourneyType;
10879         pairingsPerRound = appData.tourneyType;
10880     }
10881     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10882     gamesPerCycle = gamesPerRound * roundsPerCycle;
10883     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10884     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10885     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10886     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10887     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10888     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10889
10890     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10891     if(appData.roundSync) *syncInterval = gamesPerRound;
10892
10893     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10894
10895     if(appData.tourneyType == 0) {
10896         if(curPairing == (nPlayers-1)/2 ) {
10897             *whitePlayer = curRound;
10898             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10899         } else {
10900             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10901             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10902             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10903             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10904         }
10905     } else if(appData.tourneyType > 1) {
10906         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10907         *whitePlayer = curRound + appData.tourneyType;
10908     } else if(appData.tourneyType > 0) {
10909         *whitePlayer = curPairing;
10910         *blackPlayer = curRound + appData.tourneyType;
10911     }
10912
10913     // take care of white/black alternation per round.
10914     // For cycles and games this is already taken care of by default, derived from matchGame!
10915     return curRound & 1;
10916 }
10917
10918 int
10919 NextTourneyGame (int nr, int *swapColors)
10920 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10921     char *p, *q;
10922     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10923     FILE *tf;
10924     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10925     tf = fopen(appData.tourneyFile, "r");
10926     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10927     ParseArgsFromFile(tf); fclose(tf);
10928     InitTimeControls(); // TC might be altered from tourney file
10929
10930     nPlayers = CountPlayers(appData.participants); // count participants
10931     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10932     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10933
10934     if(syncInterval) {
10935         p = q = appData.results;
10936         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10937         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10938             DisplayMessage(_("Waiting for other game(s)"),"");
10939             waitingForGame = TRUE;
10940             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10941             return 0;
10942         }
10943         waitingForGame = FALSE;
10944     }
10945
10946     if(appData.tourneyType < 0) {
10947         if(nr>=0 && !pairingReceived) {
10948             char buf[1<<16];
10949             if(pairing.pr == NoProc) {
10950                 if(!appData.pairingEngine[0]) {
10951                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10952                     return 0;
10953                 }
10954                 StartChessProgram(&pairing); // starts the pairing engine
10955             }
10956             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10957             SendToProgram(buf, &pairing);
10958             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10959             SendToProgram(buf, &pairing);
10960             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10961         }
10962         pairingReceived = 0;                              // ... so we continue here
10963         *swapColors = 0;
10964         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10965         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10966         matchGame = 1; roundNr = nr / syncInterval + 1;
10967     }
10968
10969     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10970
10971     // redefine engines, engine dir, etc.
10972     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10973     if(first.pr == NoProc) {
10974       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10975       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10976     }
10977     if(second.pr == NoProc) {
10978       SwapEngines(1);
10979       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10980       SwapEngines(1);         // and make that valid for second engine by swapping
10981       InitEngine(&second, 1);
10982     }
10983     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10984     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10985     return OK;
10986 }
10987
10988 void
10989 NextMatchGame ()
10990 {   // performs game initialization that does not invoke engines, and then tries to start the game
10991     int res, firstWhite, swapColors = 0;
10992     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10993     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
10994         char buf[MSG_SIZ];
10995         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10996         if(strcmp(buf, currentDebugFile)) { // name has changed
10997             FILE *f = fopen(buf, "w");
10998             if(f) { // if opening the new file failed, just keep using the old one
10999                 ASSIGN(currentDebugFile, buf);
11000                 fclose(debugFP);
11001                 debugFP = f;
11002             }
11003             if(appData.serverFileName) {
11004                 if(serverFP) fclose(serverFP);
11005                 serverFP = fopen(appData.serverFileName, "w");
11006                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11007                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11008             }
11009         }
11010     }
11011     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11012     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11013     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11014     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11015     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11016     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11017     Reset(FALSE, first.pr != NoProc);
11018     res = LoadGameOrPosition(matchGame); // setup game
11019     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11020     if(!res) return; // abort when bad game/pos file
11021     TwoMachinesEvent();
11022 }
11023
11024 void
11025 UserAdjudicationEvent (int result)
11026 {
11027     ChessMove gameResult = GameIsDrawn;
11028
11029     if( result > 0 ) {
11030         gameResult = WhiteWins;
11031     }
11032     else if( result < 0 ) {
11033         gameResult = BlackWins;
11034     }
11035
11036     if( gameMode == TwoMachinesPlay ) {
11037         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11038     }
11039 }
11040
11041
11042 // [HGM] save: calculate checksum of game to make games easily identifiable
11043 int
11044 StringCheckSum (char *s)
11045 {
11046         int i = 0;
11047         if(s==NULL) return 0;
11048         while(*s) i = i*259 + *s++;
11049         return i;
11050 }
11051
11052 int
11053 GameCheckSum ()
11054 {
11055         int i, sum=0;
11056         for(i=backwardMostMove; i<forwardMostMove; i++) {
11057                 sum += pvInfoList[i].depth;
11058                 sum += StringCheckSum(parseList[i]);
11059                 sum += StringCheckSum(commentList[i]);
11060                 sum *= 261;
11061         }
11062         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11063         return sum + StringCheckSum(commentList[i]);
11064 } // end of save patch
11065
11066 void
11067 GameEnds (ChessMove result, char *resultDetails, int whosays)
11068 {
11069     GameMode nextGameMode;
11070     int isIcsGame;
11071     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11072
11073     if(endingGame) return; /* [HGM] crash: forbid recursion */
11074     endingGame = 1;
11075     if(twoBoards) { // [HGM] dual: switch back to one board
11076         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11077         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11078     }
11079     if (appData.debugMode) {
11080       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11081               result, resultDetails ? resultDetails : "(null)", whosays);
11082     }
11083
11084     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11085
11086     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11087
11088     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11089         /* If we are playing on ICS, the server decides when the
11090            game is over, but the engine can offer to draw, claim
11091            a draw, or resign.
11092          */
11093 #if ZIPPY
11094         if (appData.zippyPlay && first.initDone) {
11095             if (result == GameIsDrawn) {
11096                 /* In case draw still needs to be claimed */
11097                 SendToICS(ics_prefix);
11098                 SendToICS("draw\n");
11099             } else if (StrCaseStr(resultDetails, "resign")) {
11100                 SendToICS(ics_prefix);
11101                 SendToICS("resign\n");
11102             }
11103         }
11104 #endif
11105         endingGame = 0; /* [HGM] crash */
11106         return;
11107     }
11108
11109     /* If we're loading the game from a file, stop */
11110     if (whosays == GE_FILE) {
11111       (void) StopLoadGameTimer();
11112       gameFileFP = NULL;
11113     }
11114
11115     /* Cancel draw offers */
11116     first.offeredDraw = second.offeredDraw = 0;
11117
11118     /* If this is an ICS game, only ICS can really say it's done;
11119        if not, anyone can. */
11120     isIcsGame = (gameMode == IcsPlayingWhite ||
11121                  gameMode == IcsPlayingBlack ||
11122                  gameMode == IcsObserving    ||
11123                  gameMode == IcsExamining);
11124
11125     if (!isIcsGame || whosays == GE_ICS) {
11126         /* OK -- not an ICS game, or ICS said it was done */
11127         StopClocks();
11128         if (!isIcsGame && !appData.noChessProgram)
11129           SetUserThinkingEnables();
11130
11131         /* [HGM] if a machine claims the game end we verify this claim */
11132         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11133             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11134                 char claimer;
11135                 ChessMove trueResult = (ChessMove) -1;
11136
11137                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11138                                             first.twoMachinesColor[0] :
11139                                             second.twoMachinesColor[0] ;
11140
11141                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11142                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11143                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11144                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11145                 } else
11146                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11147                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11148                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11149                 } else
11150                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11151                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11152                 }
11153
11154                 // now verify win claims, but not in drop games, as we don't understand those yet
11155                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11156                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11157                     (result == WhiteWins && claimer == 'w' ||
11158                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11159                       if (appData.debugMode) {
11160                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11161                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11162                       }
11163                       if(result != trueResult) {
11164                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11165                               result = claimer == 'w' ? BlackWins : WhiteWins;
11166                               resultDetails = buf;
11167                       }
11168                 } else
11169                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11170                     && (forwardMostMove <= backwardMostMove ||
11171                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11172                         (claimer=='b')==(forwardMostMove&1))
11173                                                                                   ) {
11174                       /* [HGM] verify: draws that were not flagged are false claims */
11175                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11176                       result = claimer == 'w' ? BlackWins : WhiteWins;
11177                       resultDetails = buf;
11178                 }
11179                 /* (Claiming a loss is accepted no questions asked!) */
11180             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11181                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11182                 result = GameUnfinished;
11183                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11184             }
11185             /* [HGM] bare: don't allow bare King to win */
11186             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11187                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11188                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11189                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11190                && result != GameIsDrawn)
11191             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11192                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11193                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11194                         if(p >= 0 && p <= (int)WhiteKing) k++;
11195                 }
11196                 if (appData.debugMode) {
11197                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11198                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11199                 }
11200                 if(k <= 1) {
11201                         result = GameIsDrawn;
11202                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11203                         resultDetails = buf;
11204                 }
11205             }
11206         }
11207
11208
11209         if(serverMoves != NULL && !loadFlag) { char c = '=';
11210             if(result==WhiteWins) c = '+';
11211             if(result==BlackWins) c = '-';
11212             if(resultDetails != NULL)
11213                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11214         }
11215         if (resultDetails != NULL) {
11216             gameInfo.result = result;
11217             gameInfo.resultDetails = StrSave(resultDetails);
11218
11219             /* display last move only if game was not loaded from file */
11220             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11221                 DisplayMove(currentMove - 1);
11222
11223             if (forwardMostMove != 0) {
11224                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11225                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11226                                                                 ) {
11227                     if (*appData.saveGameFile != NULLCHAR) {
11228                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11229                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11230                         else
11231                         SaveGameToFile(appData.saveGameFile, TRUE);
11232                     } else if (appData.autoSaveGames) {
11233                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11234                     }
11235                     if (*appData.savePositionFile != NULLCHAR) {
11236                         SavePositionToFile(appData.savePositionFile);
11237                     }
11238                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11239                 }
11240             }
11241
11242             /* Tell program how game ended in case it is learning */
11243             /* [HGM] Moved this to after saving the PGN, just in case */
11244             /* engine died and we got here through time loss. In that */
11245             /* case we will get a fatal error writing the pipe, which */
11246             /* would otherwise lose us the PGN.                       */
11247             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11248             /* output during GameEnds should never be fatal anymore   */
11249             if (gameMode == MachinePlaysWhite ||
11250                 gameMode == MachinePlaysBlack ||
11251                 gameMode == TwoMachinesPlay ||
11252                 gameMode == IcsPlayingWhite ||
11253                 gameMode == IcsPlayingBlack ||
11254                 gameMode == BeginningOfGame) {
11255                 char buf[MSG_SIZ];
11256                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11257                         resultDetails);
11258                 if (first.pr != NoProc) {
11259                     SendToProgram(buf, &first);
11260                 }
11261                 if (second.pr != NoProc &&
11262                     gameMode == TwoMachinesPlay) {
11263                     SendToProgram(buf, &second);
11264                 }
11265             }
11266         }
11267
11268         if (appData.icsActive) {
11269             if (appData.quietPlay &&
11270                 (gameMode == IcsPlayingWhite ||
11271                  gameMode == IcsPlayingBlack)) {
11272                 SendToICS(ics_prefix);
11273                 SendToICS("set shout 1\n");
11274             }
11275             nextGameMode = IcsIdle;
11276             ics_user_moved = FALSE;
11277             /* clean up premove.  It's ugly when the game has ended and the
11278              * premove highlights are still on the board.
11279              */
11280             if (gotPremove) {
11281               gotPremove = FALSE;
11282               ClearPremoveHighlights();
11283               DrawPosition(FALSE, boards[currentMove]);
11284             }
11285             if (whosays == GE_ICS) {
11286                 switch (result) {
11287                 case WhiteWins:
11288                     if (gameMode == IcsPlayingWhite)
11289                         PlayIcsWinSound();
11290                     else if(gameMode == IcsPlayingBlack)
11291                         PlayIcsLossSound();
11292                     break;
11293                 case BlackWins:
11294                     if (gameMode == IcsPlayingBlack)
11295                         PlayIcsWinSound();
11296                     else if(gameMode == IcsPlayingWhite)
11297                         PlayIcsLossSound();
11298                     break;
11299                 case GameIsDrawn:
11300                     PlayIcsDrawSound();
11301                     break;
11302                 default:
11303                     PlayIcsUnfinishedSound();
11304                 }
11305             }
11306             if(appData.quitNext) { ExitEvent(0); return; }
11307         } else if (gameMode == EditGame ||
11308                    gameMode == PlayFromGameFile ||
11309                    gameMode == AnalyzeMode ||
11310                    gameMode == AnalyzeFile) {
11311             nextGameMode = gameMode;
11312         } else {
11313             nextGameMode = EndOfGame;
11314         }
11315         pausing = FALSE;
11316         ModeHighlight();
11317     } else {
11318         nextGameMode = gameMode;
11319     }
11320
11321     if (appData.noChessProgram) {
11322         gameMode = nextGameMode;
11323         ModeHighlight();
11324         endingGame = 0; /* [HGM] crash */
11325         return;
11326     }
11327
11328     if (first.reuse) {
11329         /* Put first chess program into idle state */
11330         if (first.pr != NoProc &&
11331             (gameMode == MachinePlaysWhite ||
11332              gameMode == MachinePlaysBlack ||
11333              gameMode == TwoMachinesPlay ||
11334              gameMode == IcsPlayingWhite ||
11335              gameMode == IcsPlayingBlack ||
11336              gameMode == BeginningOfGame)) {
11337             SendToProgram("force\n", &first);
11338             if (first.usePing) {
11339               char buf[MSG_SIZ];
11340               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11341               SendToProgram(buf, &first);
11342             }
11343         }
11344     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11345         /* Kill off first chess program */
11346         if (first.isr != NULL)
11347           RemoveInputSource(first.isr);
11348         first.isr = NULL;
11349
11350         if (first.pr != NoProc) {
11351             ExitAnalyzeMode();
11352             DoSleep( appData.delayBeforeQuit );
11353             SendToProgram("quit\n", &first);
11354             DoSleep( appData.delayAfterQuit );
11355             DestroyChildProcess(first.pr, first.useSigterm);
11356             first.reload = TRUE;
11357         }
11358         first.pr = NoProc;
11359     }
11360     if (second.reuse) {
11361         /* Put second chess program into idle state */
11362         if (second.pr != NoProc &&
11363             gameMode == TwoMachinesPlay) {
11364             SendToProgram("force\n", &second);
11365             if (second.usePing) {
11366               char buf[MSG_SIZ];
11367               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11368               SendToProgram(buf, &second);
11369             }
11370         }
11371     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11372         /* Kill off second chess program */
11373         if (second.isr != NULL)
11374           RemoveInputSource(second.isr);
11375         second.isr = NULL;
11376
11377         if (second.pr != NoProc) {
11378             DoSleep( appData.delayBeforeQuit );
11379             SendToProgram("quit\n", &second);
11380             DoSleep( appData.delayAfterQuit );
11381             DestroyChildProcess(second.pr, second.useSigterm);
11382             second.reload = TRUE;
11383         }
11384         second.pr = NoProc;
11385     }
11386
11387     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11388         char resChar = '=';
11389         switch (result) {
11390         case WhiteWins:
11391           resChar = '+';
11392           if (first.twoMachinesColor[0] == 'w') {
11393             first.matchWins++;
11394           } else {
11395             second.matchWins++;
11396           }
11397           break;
11398         case BlackWins:
11399           resChar = '-';
11400           if (first.twoMachinesColor[0] == 'b') {
11401             first.matchWins++;
11402           } else {
11403             second.matchWins++;
11404           }
11405           break;
11406         case GameUnfinished:
11407           resChar = ' ';
11408         default:
11409           break;
11410         }
11411
11412         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11413         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11414             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11415             ReserveGame(nextGame, resChar); // sets nextGame
11416             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11417             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11418         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11419
11420         if (nextGame <= appData.matchGames && !abortMatch) {
11421             gameMode = nextGameMode;
11422             matchGame = nextGame; // this will be overruled in tourney mode!
11423             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11424             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11425             endingGame = 0; /* [HGM] crash */
11426             return;
11427         } else {
11428             gameMode = nextGameMode;
11429             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11430                      first.tidy, second.tidy,
11431                      first.matchWins, second.matchWins,
11432                      appData.matchGames - (first.matchWins + second.matchWins));
11433             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11434             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11435             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11436             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11437                 first.twoMachinesColor = "black\n";
11438                 second.twoMachinesColor = "white\n";
11439             } else {
11440                 first.twoMachinesColor = "white\n";
11441                 second.twoMachinesColor = "black\n";
11442             }
11443         }
11444     }
11445     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11446         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11447       ExitAnalyzeMode();
11448     gameMode = nextGameMode;
11449     ModeHighlight();
11450     endingGame = 0;  /* [HGM] crash */
11451     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11452         if(matchMode == TRUE) { // match through command line: exit with or without popup
11453             if(ranking) {
11454                 ToNrEvent(forwardMostMove);
11455                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11456                 else ExitEvent(0);
11457             } else DisplayFatalError(buf, 0, 0);
11458         } else { // match through menu; just stop, with or without popup
11459             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11460             ModeHighlight();
11461             if(ranking){
11462                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11463             } else DisplayNote(buf);
11464       }
11465       if(ranking) free(ranking);
11466     }
11467 }
11468
11469 /* Assumes program was just initialized (initString sent).
11470    Leaves program in force mode. */
11471 void
11472 FeedMovesToProgram (ChessProgramState *cps, int upto)
11473 {
11474     int i;
11475
11476     if (appData.debugMode)
11477       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11478               startedFromSetupPosition ? "position and " : "",
11479               backwardMostMove, upto, cps->which);
11480     if(currentlyInitializedVariant != gameInfo.variant) {
11481       char buf[MSG_SIZ];
11482         // [HGM] variantswitch: make engine aware of new variant
11483         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11484                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11485                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11486         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11487         SendToProgram(buf, cps);
11488         currentlyInitializedVariant = gameInfo.variant;
11489     }
11490     SendToProgram("force\n", cps);
11491     if (startedFromSetupPosition) {
11492         SendBoard(cps, backwardMostMove);
11493     if (appData.debugMode) {
11494         fprintf(debugFP, "feedMoves\n");
11495     }
11496     }
11497     for (i = backwardMostMove; i < upto; i++) {
11498         SendMoveToProgram(i, cps);
11499     }
11500 }
11501
11502
11503 int
11504 ResurrectChessProgram ()
11505 {
11506      /* The chess program may have exited.
11507         If so, restart it and feed it all the moves made so far. */
11508     static int doInit = 0;
11509
11510     if (appData.noChessProgram) return 1;
11511
11512     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11513         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11514         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11515         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11516     } else {
11517         if (first.pr != NoProc) return 1;
11518         StartChessProgram(&first);
11519     }
11520     InitChessProgram(&first, FALSE);
11521     FeedMovesToProgram(&first, currentMove);
11522
11523     if (!first.sendTime) {
11524         /* can't tell gnuchess what its clock should read,
11525            so we bow to its notion. */
11526         ResetClocks();
11527         timeRemaining[0][currentMove] = whiteTimeRemaining;
11528         timeRemaining[1][currentMove] = blackTimeRemaining;
11529     }
11530
11531     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11532                 appData.icsEngineAnalyze) && first.analysisSupport) {
11533       SendToProgram("analyze\n", &first);
11534       first.analyzing = TRUE;
11535     }
11536     return 1;
11537 }
11538
11539 /*
11540  * Button procedures
11541  */
11542 void
11543 Reset (int redraw, int init)
11544 {
11545     int i;
11546
11547     if (appData.debugMode) {
11548         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11549                 redraw, init, gameMode);
11550     }
11551     CleanupTail(); // [HGM] vari: delete any stored variations
11552     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11553     pausing = pauseExamInvalid = FALSE;
11554     startedFromSetupPosition = blackPlaysFirst = FALSE;
11555     firstMove = TRUE;
11556     whiteFlag = blackFlag = FALSE;
11557     userOfferedDraw = FALSE;
11558     hintRequested = bookRequested = FALSE;
11559     first.maybeThinking = FALSE;
11560     second.maybeThinking = FALSE;
11561     first.bookSuspend = FALSE; // [HGM] book
11562     second.bookSuspend = FALSE;
11563     thinkOutput[0] = NULLCHAR;
11564     lastHint[0] = NULLCHAR;
11565     ClearGameInfo(&gameInfo);
11566     gameInfo.variant = StringToVariant(appData.variant);
11567     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11568     ics_user_moved = ics_clock_paused = FALSE;
11569     ics_getting_history = H_FALSE;
11570     ics_gamenum = -1;
11571     white_holding[0] = black_holding[0] = NULLCHAR;
11572     ClearProgramStats();
11573     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11574
11575     ResetFrontEnd();
11576     ClearHighlights();
11577     flipView = appData.flipView;
11578     ClearPremoveHighlights();
11579     gotPremove = FALSE;
11580     alarmSounded = FALSE;
11581     killX = killY = -1; // [HGM] lion
11582
11583     GameEnds(EndOfFile, NULL, GE_PLAYER);
11584     if(appData.serverMovesName != NULL) {
11585         /* [HGM] prepare to make moves file for broadcasting */
11586         clock_t t = clock();
11587         if(serverMoves != NULL) fclose(serverMoves);
11588         serverMoves = fopen(appData.serverMovesName, "r");
11589         if(serverMoves != NULL) {
11590             fclose(serverMoves);
11591             /* delay 15 sec before overwriting, so all clients can see end */
11592             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11593         }
11594         serverMoves = fopen(appData.serverMovesName, "w");
11595     }
11596
11597     ExitAnalyzeMode();
11598     gameMode = BeginningOfGame;
11599     ModeHighlight();
11600     if(appData.icsActive) gameInfo.variant = VariantNormal;
11601     currentMove = forwardMostMove = backwardMostMove = 0;
11602     MarkTargetSquares(1);
11603     InitPosition(redraw);
11604     for (i = 0; i < MAX_MOVES; i++) {
11605         if (commentList[i] != NULL) {
11606             free(commentList[i]);
11607             commentList[i] = NULL;
11608         }
11609     }
11610     ResetClocks();
11611     timeRemaining[0][0] = whiteTimeRemaining;
11612     timeRemaining[1][0] = blackTimeRemaining;
11613
11614     if (first.pr == NoProc) {
11615         StartChessProgram(&first);
11616     }
11617     if (init) {
11618             InitChessProgram(&first, startedFromSetupPosition);
11619     }
11620     DisplayTitle("");
11621     DisplayMessage("", "");
11622     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11623     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11624     ClearMap();        // [HGM] exclude: invalidate map
11625 }
11626
11627 void
11628 AutoPlayGameLoop ()
11629 {
11630     for (;;) {
11631         if (!AutoPlayOneMove())
11632           return;
11633         if (matchMode || appData.timeDelay == 0)
11634           continue;
11635         if (appData.timeDelay < 0)
11636           return;
11637         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11638         break;
11639     }
11640 }
11641
11642 void
11643 AnalyzeNextGame()
11644 {
11645     ReloadGame(1); // next game
11646 }
11647
11648 int
11649 AutoPlayOneMove ()
11650 {
11651     int fromX, fromY, toX, toY;
11652
11653     if (appData.debugMode) {
11654       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11655     }
11656
11657     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11658       return FALSE;
11659
11660     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11661       pvInfoList[currentMove].depth = programStats.depth;
11662       pvInfoList[currentMove].score = programStats.score;
11663       pvInfoList[currentMove].time  = 0;
11664       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11665       else { // append analysis of final position as comment
11666         char buf[MSG_SIZ];
11667         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11668         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11669       }
11670       programStats.depth = 0;
11671     }
11672
11673     if (currentMove >= forwardMostMove) {
11674       if(gameMode == AnalyzeFile) {
11675           if(appData.loadGameIndex == -1) {
11676             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11677           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11678           } else {
11679           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11680         }
11681       }
11682 //      gameMode = EndOfGame;
11683 //      ModeHighlight();
11684
11685       /* [AS] Clear current move marker at the end of a game */
11686       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11687
11688       return FALSE;
11689     }
11690
11691     toX = moveList[currentMove][2] - AAA;
11692     toY = moveList[currentMove][3] - ONE;
11693
11694     if (moveList[currentMove][1] == '@') {
11695         if (appData.highlightLastMove) {
11696             SetHighlights(-1, -1, toX, toY);
11697         }
11698     } else {
11699         fromX = moveList[currentMove][0] - AAA;
11700         fromY = moveList[currentMove][1] - ONE;
11701
11702         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11703
11704         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11705
11706         if (appData.highlightLastMove) {
11707             SetHighlights(fromX, fromY, toX, toY);
11708         }
11709     }
11710     DisplayMove(currentMove);
11711     SendMoveToProgram(currentMove++, &first);
11712     DisplayBothClocks();
11713     DrawPosition(FALSE, boards[currentMove]);
11714     // [HGM] PV info: always display, routine tests if empty
11715     DisplayComment(currentMove - 1, commentList[currentMove]);
11716     return TRUE;
11717 }
11718
11719
11720 int
11721 LoadGameOneMove (ChessMove readAhead)
11722 {
11723     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11724     char promoChar = NULLCHAR;
11725     ChessMove moveType;
11726     char move[MSG_SIZ];
11727     char *p, *q;
11728
11729     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11730         gameMode != AnalyzeMode && gameMode != Training) {
11731         gameFileFP = NULL;
11732         return FALSE;
11733     }
11734
11735     yyboardindex = forwardMostMove;
11736     if (readAhead != EndOfFile) {
11737       moveType = readAhead;
11738     } else {
11739       if (gameFileFP == NULL)
11740           return FALSE;
11741       moveType = (ChessMove) Myylex();
11742     }
11743
11744     done = FALSE;
11745     switch (moveType) {
11746       case Comment:
11747         if (appData.debugMode)
11748           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11749         p = yy_text;
11750
11751         /* append the comment but don't display it */
11752         AppendComment(currentMove, p, FALSE);
11753         return TRUE;
11754
11755       case WhiteCapturesEnPassant:
11756       case BlackCapturesEnPassant:
11757       case WhitePromotion:
11758       case BlackPromotion:
11759       case WhiteNonPromotion:
11760       case BlackNonPromotion:
11761       case NormalMove:
11762       case FirstLeg:
11763       case WhiteKingSideCastle:
11764       case WhiteQueenSideCastle:
11765       case BlackKingSideCastle:
11766       case BlackQueenSideCastle:
11767       case WhiteKingSideCastleWild:
11768       case WhiteQueenSideCastleWild:
11769       case BlackKingSideCastleWild:
11770       case BlackQueenSideCastleWild:
11771       /* PUSH Fabien */
11772       case WhiteHSideCastleFR:
11773       case WhiteASideCastleFR:
11774       case BlackHSideCastleFR:
11775       case BlackASideCastleFR:
11776       /* POP Fabien */
11777         if (appData.debugMode)
11778           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11779         fromX = currentMoveString[0] - AAA;
11780         fromY = currentMoveString[1] - ONE;
11781         toX = currentMoveString[2] - AAA;
11782         toY = currentMoveString[3] - ONE;
11783         promoChar = currentMoveString[4];
11784         if(promoChar == ';') promoChar = NULLCHAR;
11785         break;
11786
11787       case WhiteDrop:
11788       case BlackDrop:
11789         if (appData.debugMode)
11790           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11791         fromX = moveType == WhiteDrop ?
11792           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11793         (int) CharToPiece(ToLower(currentMoveString[0]));
11794         fromY = DROP_RANK;
11795         toX = currentMoveString[2] - AAA;
11796         toY = currentMoveString[3] - ONE;
11797         break;
11798
11799       case WhiteWins:
11800       case BlackWins:
11801       case GameIsDrawn:
11802       case GameUnfinished:
11803         if (appData.debugMode)
11804           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11805         p = strchr(yy_text, '{');
11806         if (p == NULL) p = strchr(yy_text, '(');
11807         if (p == NULL) {
11808             p = yy_text;
11809             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11810         } else {
11811             q = strchr(p, *p == '{' ? '}' : ')');
11812             if (q != NULL) *q = NULLCHAR;
11813             p++;
11814         }
11815         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11816         GameEnds(moveType, p, GE_FILE);
11817         done = TRUE;
11818         if (cmailMsgLoaded) {
11819             ClearHighlights();
11820             flipView = WhiteOnMove(currentMove);
11821             if (moveType == GameUnfinished) flipView = !flipView;
11822             if (appData.debugMode)
11823               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11824         }
11825         break;
11826
11827       case EndOfFile:
11828         if (appData.debugMode)
11829           fprintf(debugFP, "Parser hit end of file\n");
11830         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11831           case MT_NONE:
11832           case MT_CHECK:
11833             break;
11834           case MT_CHECKMATE:
11835           case MT_STAINMATE:
11836             if (WhiteOnMove(currentMove)) {
11837                 GameEnds(BlackWins, "Black mates", GE_FILE);
11838             } else {
11839                 GameEnds(WhiteWins, "White mates", GE_FILE);
11840             }
11841             break;
11842           case MT_STALEMATE:
11843             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11844             break;
11845         }
11846         done = TRUE;
11847         break;
11848
11849       case MoveNumberOne:
11850         if (lastLoadGameStart == GNUChessGame) {
11851             /* GNUChessGames have numbers, but they aren't move numbers */
11852             if (appData.debugMode)
11853               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11854                       yy_text, (int) moveType);
11855             return LoadGameOneMove(EndOfFile); /* tail recursion */
11856         }
11857         /* else fall thru */
11858
11859       case XBoardGame:
11860       case GNUChessGame:
11861       case PGNTag:
11862         /* Reached start of next game in file */
11863         if (appData.debugMode)
11864           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11865         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11866           case MT_NONE:
11867           case MT_CHECK:
11868             break;
11869           case MT_CHECKMATE:
11870           case MT_STAINMATE:
11871             if (WhiteOnMove(currentMove)) {
11872                 GameEnds(BlackWins, "Black mates", GE_FILE);
11873             } else {
11874                 GameEnds(WhiteWins, "White mates", GE_FILE);
11875             }
11876             break;
11877           case MT_STALEMATE:
11878             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11879             break;
11880         }
11881         done = TRUE;
11882         break;
11883
11884       case PositionDiagram:     /* should not happen; ignore */
11885       case ElapsedTime:         /* ignore */
11886       case NAG:                 /* ignore */
11887         if (appData.debugMode)
11888           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11889                   yy_text, (int) moveType);
11890         return LoadGameOneMove(EndOfFile); /* tail recursion */
11891
11892       case IllegalMove:
11893         if (appData.testLegality) {
11894             if (appData.debugMode)
11895               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11896             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11897                     (forwardMostMove / 2) + 1,
11898                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11899             DisplayError(move, 0);
11900             done = TRUE;
11901         } else {
11902             if (appData.debugMode)
11903               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11904                       yy_text, currentMoveString);
11905             fromX = currentMoveString[0] - AAA;
11906             fromY = currentMoveString[1] - ONE;
11907             toX = currentMoveString[2] - AAA;
11908             toY = currentMoveString[3] - ONE;
11909             promoChar = currentMoveString[4];
11910         }
11911         break;
11912
11913       case AmbiguousMove:
11914         if (appData.debugMode)
11915           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11916         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11917                 (forwardMostMove / 2) + 1,
11918                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11919         DisplayError(move, 0);
11920         done = TRUE;
11921         break;
11922
11923       default:
11924       case ImpossibleMove:
11925         if (appData.debugMode)
11926           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11927         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11928                 (forwardMostMove / 2) + 1,
11929                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11930         DisplayError(move, 0);
11931         done = TRUE;
11932         break;
11933     }
11934
11935     if (done) {
11936         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11937             DrawPosition(FALSE, boards[currentMove]);
11938             DisplayBothClocks();
11939             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11940               DisplayComment(currentMove - 1, commentList[currentMove]);
11941         }
11942         (void) StopLoadGameTimer();
11943         gameFileFP = NULL;
11944         cmailOldMove = forwardMostMove;
11945         return FALSE;
11946     } else {
11947         /* currentMoveString is set as a side-effect of yylex */
11948
11949         thinkOutput[0] = NULLCHAR;
11950         MakeMove(fromX, fromY, toX, toY, promoChar);
11951         killX = killY = -1; // [HGM] lion: used up
11952         currentMove = forwardMostMove;
11953         return TRUE;
11954     }
11955 }
11956
11957 /* Load the nth game from the given file */
11958 int
11959 LoadGameFromFile (char *filename, int n, char *title, int useList)
11960 {
11961     FILE *f;
11962     char buf[MSG_SIZ];
11963
11964     if (strcmp(filename, "-") == 0) {
11965         f = stdin;
11966         title = "stdin";
11967     } else {
11968         f = fopen(filename, "rb");
11969         if (f == NULL) {
11970           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11971             DisplayError(buf, errno);
11972             return FALSE;
11973         }
11974     }
11975     if (fseek(f, 0, 0) == -1) {
11976         /* f is not seekable; probably a pipe */
11977         useList = FALSE;
11978     }
11979     if (useList && n == 0) {
11980         int error = GameListBuild(f);
11981         if (error) {
11982             DisplayError(_("Cannot build game list"), error);
11983         } else if (!ListEmpty(&gameList) &&
11984                    ((ListGame *) gameList.tailPred)->number > 1) {
11985             GameListPopUp(f, title);
11986             return TRUE;
11987         }
11988         GameListDestroy();
11989         n = 1;
11990     }
11991     if (n == 0) n = 1;
11992     return LoadGame(f, n, title, FALSE);
11993 }
11994
11995
11996 void
11997 MakeRegisteredMove ()
11998 {
11999     int fromX, fromY, toX, toY;
12000     char promoChar;
12001     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12002         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12003           case CMAIL_MOVE:
12004           case CMAIL_DRAW:
12005             if (appData.debugMode)
12006               fprintf(debugFP, "Restoring %s for game %d\n",
12007                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12008
12009             thinkOutput[0] = NULLCHAR;
12010             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12011             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12012             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12013             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12014             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12015             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12016             MakeMove(fromX, fromY, toX, toY, promoChar);
12017             ShowMove(fromX, fromY, toX, toY);
12018
12019             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12020               case MT_NONE:
12021               case MT_CHECK:
12022                 break;
12023
12024               case MT_CHECKMATE:
12025               case MT_STAINMATE:
12026                 if (WhiteOnMove(currentMove)) {
12027                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12028                 } else {
12029                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12030                 }
12031                 break;
12032
12033               case MT_STALEMATE:
12034                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12035                 break;
12036             }
12037
12038             break;
12039
12040           case CMAIL_RESIGN:
12041             if (WhiteOnMove(currentMove)) {
12042                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12043             } else {
12044                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12045             }
12046             break;
12047
12048           case CMAIL_ACCEPT:
12049             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12050             break;
12051
12052           default:
12053             break;
12054         }
12055     }
12056
12057     return;
12058 }
12059
12060 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12061 int
12062 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12063 {
12064     int retVal;
12065
12066     if (gameNumber > nCmailGames) {
12067         DisplayError(_("No more games in this message"), 0);
12068         return FALSE;
12069     }
12070     if (f == lastLoadGameFP) {
12071         int offset = gameNumber - lastLoadGameNumber;
12072         if (offset == 0) {
12073             cmailMsg[0] = NULLCHAR;
12074             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12075                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12076                 nCmailMovesRegistered--;
12077             }
12078             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12079             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12080                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12081             }
12082         } else {
12083             if (! RegisterMove()) return FALSE;
12084         }
12085     }
12086
12087     retVal = LoadGame(f, gameNumber, title, useList);
12088
12089     /* Make move registered during previous look at this game, if any */
12090     MakeRegisteredMove();
12091
12092     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12093         commentList[currentMove]
12094           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12095         DisplayComment(currentMove - 1, commentList[currentMove]);
12096     }
12097
12098     return retVal;
12099 }
12100
12101 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12102 int
12103 ReloadGame (int offset)
12104 {
12105     int gameNumber = lastLoadGameNumber + offset;
12106     if (lastLoadGameFP == NULL) {
12107         DisplayError(_("No game has been loaded yet"), 0);
12108         return FALSE;
12109     }
12110     if (gameNumber <= 0) {
12111         DisplayError(_("Can't back up any further"), 0);
12112         return FALSE;
12113     }
12114     if (cmailMsgLoaded) {
12115         return CmailLoadGame(lastLoadGameFP, gameNumber,
12116                              lastLoadGameTitle, lastLoadGameUseList);
12117     } else {
12118         return LoadGame(lastLoadGameFP, gameNumber,
12119                         lastLoadGameTitle, lastLoadGameUseList);
12120     }
12121 }
12122
12123 int keys[EmptySquare+1];
12124
12125 int
12126 PositionMatches (Board b1, Board b2)
12127 {
12128     int r, f, sum=0;
12129     switch(appData.searchMode) {
12130         case 1: return CompareWithRights(b1, b2);
12131         case 2:
12132             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12133                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12134             }
12135             return TRUE;
12136         case 3:
12137             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12138               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12139                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12140             }
12141             return sum==0;
12142         case 4:
12143             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12144                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12145             }
12146             return sum==0;
12147     }
12148     return TRUE;
12149 }
12150
12151 #define Q_PROMO  4
12152 #define Q_EP     3
12153 #define Q_BCASTL 2
12154 #define Q_WCASTL 1
12155
12156 int pieceList[256], quickBoard[256];
12157 ChessSquare pieceType[256] = { EmptySquare };
12158 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12159 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12160 int soughtTotal, turn;
12161 Boolean epOK, flipSearch;
12162
12163 typedef struct {
12164     unsigned char piece, to;
12165 } Move;
12166
12167 #define DSIZE (250000)
12168
12169 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12170 Move *moveDatabase = initialSpace;
12171 unsigned int movePtr, dataSize = DSIZE;
12172
12173 int
12174 MakePieceList (Board board, int *counts)
12175 {
12176     int r, f, n=Q_PROMO, total=0;
12177     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12178     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12179         int sq = f + (r<<4);
12180         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12181             quickBoard[sq] = ++n;
12182             pieceList[n] = sq;
12183             pieceType[n] = board[r][f];
12184             counts[board[r][f]]++;
12185             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12186             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12187             total++;
12188         }
12189     }
12190     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12191     return total;
12192 }
12193
12194 void
12195 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12196 {
12197     int sq = fromX + (fromY<<4);
12198     int piece = quickBoard[sq];
12199     quickBoard[sq] = 0;
12200     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12201     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12202         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12203         moveDatabase[movePtr++].piece = Q_WCASTL;
12204         quickBoard[sq] = piece;
12205         piece = quickBoard[from]; quickBoard[from] = 0;
12206         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12207     } else
12208     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12209         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12210         moveDatabase[movePtr++].piece = Q_BCASTL;
12211         quickBoard[sq] = piece;
12212         piece = quickBoard[from]; quickBoard[from] = 0;
12213         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12214     } else
12215     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12216         quickBoard[(fromY<<4)+toX] = 0;
12217         moveDatabase[movePtr].piece = Q_EP;
12218         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12219         moveDatabase[movePtr].to = sq;
12220     } else
12221     if(promoPiece != pieceType[piece]) {
12222         moveDatabase[movePtr++].piece = Q_PROMO;
12223         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12224     }
12225     moveDatabase[movePtr].piece = piece;
12226     quickBoard[sq] = piece;
12227     movePtr++;
12228 }
12229
12230 int
12231 PackGame (Board board)
12232 {
12233     Move *newSpace = NULL;
12234     moveDatabase[movePtr].piece = 0; // terminate previous game
12235     if(movePtr > dataSize) {
12236         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12237         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12238         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12239         if(newSpace) {
12240             int i;
12241             Move *p = moveDatabase, *q = newSpace;
12242             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12243             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12244             moveDatabase = newSpace;
12245         } else { // calloc failed, we must be out of memory. Too bad...
12246             dataSize = 0; // prevent calloc events for all subsequent games
12247             return 0;     // and signal this one isn't cached
12248         }
12249     }
12250     movePtr++;
12251     MakePieceList(board, counts);
12252     return movePtr;
12253 }
12254
12255 int
12256 QuickCompare (Board board, int *minCounts, int *maxCounts)
12257 {   // compare according to search mode
12258     int r, f;
12259     switch(appData.searchMode)
12260     {
12261       case 1: // exact position match
12262         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12263         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12264             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12265         }
12266         break;
12267       case 2: // can have extra material on empty squares
12268         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12269             if(board[r][f] == EmptySquare) continue;
12270             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12271         }
12272         break;
12273       case 3: // material with exact Pawn structure
12274         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12275             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12276             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12277         } // fall through to material comparison
12278       case 4: // exact material
12279         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12280         break;
12281       case 6: // material range with given imbalance
12282         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12283         // fall through to range comparison
12284       case 5: // material range
12285         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12286     }
12287     return TRUE;
12288 }
12289
12290 int
12291 QuickScan (Board board, Move *move)
12292 {   // reconstruct game,and compare all positions in it
12293     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12294     do {
12295         int piece = move->piece;
12296         int to = move->to, from = pieceList[piece];
12297         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12298           if(!piece) return -1;
12299           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12300             piece = (++move)->piece;
12301             from = pieceList[piece];
12302             counts[pieceType[piece]]--;
12303             pieceType[piece] = (ChessSquare) move->to;
12304             counts[move->to]++;
12305           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12306             counts[pieceType[quickBoard[to]]]--;
12307             quickBoard[to] = 0; total--;
12308             move++;
12309             continue;
12310           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12311             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12312             from  = pieceList[piece]; // so this must be King
12313             quickBoard[from] = 0;
12314             pieceList[piece] = to;
12315             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12316             quickBoard[from] = 0; // rook
12317             quickBoard[to] = piece;
12318             to = move->to; piece = move->piece;
12319             goto aftercastle;
12320           }
12321         }
12322         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12323         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12324         quickBoard[from] = 0;
12325       aftercastle:
12326         quickBoard[to] = piece;
12327         pieceList[piece] = to;
12328         cnt++; turn ^= 3;
12329         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12330            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12331            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12332                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12333           ) {
12334             static int lastCounts[EmptySquare+1];
12335             int i;
12336             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12337             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12338         } else stretch = 0;
12339         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12340         move++;
12341     } while(1);
12342 }
12343
12344 void
12345 InitSearch ()
12346 {
12347     int r, f;
12348     flipSearch = FALSE;
12349     CopyBoard(soughtBoard, boards[currentMove]);
12350     soughtTotal = MakePieceList(soughtBoard, maxSought);
12351     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12352     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12353     CopyBoard(reverseBoard, boards[currentMove]);
12354     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12355         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12356         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12357         reverseBoard[r][f] = piece;
12358     }
12359     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12360     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12361     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12362                  || (boards[currentMove][CASTLING][2] == NoRights ||
12363                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12364                  && (boards[currentMove][CASTLING][5] == NoRights ||
12365                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12366       ) {
12367         flipSearch = TRUE;
12368         CopyBoard(flipBoard, soughtBoard);
12369         CopyBoard(rotateBoard, reverseBoard);
12370         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12371             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12372             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12373         }
12374     }
12375     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12376     if(appData.searchMode >= 5) {
12377         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12378         MakePieceList(soughtBoard, minSought);
12379         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12380     }
12381     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12382         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12383 }
12384
12385 GameInfo dummyInfo;
12386 static int creatingBook;
12387
12388 int
12389 GameContainsPosition (FILE *f, ListGame *lg)
12390 {
12391     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12392     int fromX, fromY, toX, toY;
12393     char promoChar;
12394     static int initDone=FALSE;
12395
12396     // weed out games based on numerical tag comparison
12397     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12398     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12399     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12400     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12401     if(!initDone) {
12402         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12403         initDone = TRUE;
12404     }
12405     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12406     else CopyBoard(boards[scratch], initialPosition); // default start position
12407     if(lg->moves) {
12408         turn = btm + 1;
12409         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12410         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12411     }
12412     if(btm) plyNr++;
12413     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12414     fseek(f, lg->offset, 0);
12415     yynewfile(f);
12416     while(1) {
12417         yyboardindex = scratch;
12418         quickFlag = plyNr+1;
12419         next = Myylex();
12420         quickFlag = 0;
12421         switch(next) {
12422             case PGNTag:
12423                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12424             default:
12425                 continue;
12426
12427             case XBoardGame:
12428             case GNUChessGame:
12429                 if(plyNr) return -1; // after we have seen moves, this is for new game
12430               continue;
12431
12432             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12433             case ImpossibleMove:
12434             case WhiteWins: // game ends here with these four
12435             case BlackWins:
12436             case GameIsDrawn:
12437             case GameUnfinished:
12438                 return -1;
12439
12440             case IllegalMove:
12441                 if(appData.testLegality) return -1;
12442             case WhiteCapturesEnPassant:
12443             case BlackCapturesEnPassant:
12444             case WhitePromotion:
12445             case BlackPromotion:
12446             case WhiteNonPromotion:
12447             case BlackNonPromotion:
12448             case NormalMove:
12449             case FirstLeg:
12450             case WhiteKingSideCastle:
12451             case WhiteQueenSideCastle:
12452             case BlackKingSideCastle:
12453             case BlackQueenSideCastle:
12454             case WhiteKingSideCastleWild:
12455             case WhiteQueenSideCastleWild:
12456             case BlackKingSideCastleWild:
12457             case BlackQueenSideCastleWild:
12458             case WhiteHSideCastleFR:
12459             case WhiteASideCastleFR:
12460             case BlackHSideCastleFR:
12461             case BlackASideCastleFR:
12462                 fromX = currentMoveString[0] - AAA;
12463                 fromY = currentMoveString[1] - ONE;
12464                 toX = currentMoveString[2] - AAA;
12465                 toY = currentMoveString[3] - ONE;
12466                 promoChar = currentMoveString[4];
12467                 break;
12468             case WhiteDrop:
12469             case BlackDrop:
12470                 fromX = next == WhiteDrop ?
12471                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12472                   (int) CharToPiece(ToLower(currentMoveString[0]));
12473                 fromY = DROP_RANK;
12474                 toX = currentMoveString[2] - AAA;
12475                 toY = currentMoveString[3] - ONE;
12476                 promoChar = 0;
12477                 break;
12478         }
12479         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12480         plyNr++;
12481         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12482         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12483         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12484         if(appData.findMirror) {
12485             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12486             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12487         }
12488     }
12489 }
12490
12491 /* Load the nth game from open file f */
12492 int
12493 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12494 {
12495     ChessMove cm;
12496     char buf[MSG_SIZ];
12497     int gn = gameNumber;
12498     ListGame *lg = NULL;
12499     int numPGNTags = 0;
12500     int err, pos = -1;
12501     GameMode oldGameMode;
12502     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12503
12504     if (appData.debugMode)
12505         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12506
12507     if (gameMode == Training )
12508         SetTrainingModeOff();
12509
12510     oldGameMode = gameMode;
12511     if (gameMode != BeginningOfGame) {
12512       Reset(FALSE, TRUE);
12513     }
12514     killX = killY = -1; // [HGM] lion: in case we did not Reset
12515
12516     gameFileFP = f;
12517     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12518         fclose(lastLoadGameFP);
12519     }
12520
12521     if (useList) {
12522         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12523
12524         if (lg) {
12525             fseek(f, lg->offset, 0);
12526             GameListHighlight(gameNumber);
12527             pos = lg->position;
12528             gn = 1;
12529         }
12530         else {
12531             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12532               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12533             else
12534             DisplayError(_("Game number out of range"), 0);
12535             return FALSE;
12536         }
12537     } else {
12538         GameListDestroy();
12539         if (fseek(f, 0, 0) == -1) {
12540             if (f == lastLoadGameFP ?
12541                 gameNumber == lastLoadGameNumber + 1 :
12542                 gameNumber == 1) {
12543                 gn = 1;
12544             } else {
12545                 DisplayError(_("Can't seek on game file"), 0);
12546                 return FALSE;
12547             }
12548         }
12549     }
12550     lastLoadGameFP = f;
12551     lastLoadGameNumber = gameNumber;
12552     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12553     lastLoadGameUseList = useList;
12554
12555     yynewfile(f);
12556
12557     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12558       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12559                 lg->gameInfo.black);
12560             DisplayTitle(buf);
12561     } else if (*title != NULLCHAR) {
12562         if (gameNumber > 1) {
12563           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12564             DisplayTitle(buf);
12565         } else {
12566             DisplayTitle(title);
12567         }
12568     }
12569
12570     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12571         gameMode = PlayFromGameFile;
12572         ModeHighlight();
12573     }
12574
12575     currentMove = forwardMostMove = backwardMostMove = 0;
12576     CopyBoard(boards[0], initialPosition);
12577     StopClocks();
12578
12579     /*
12580      * Skip the first gn-1 games in the file.
12581      * Also skip over anything that precedes an identifiable
12582      * start of game marker, to avoid being confused by
12583      * garbage at the start of the file.  Currently
12584      * recognized start of game markers are the move number "1",
12585      * the pattern "gnuchess .* game", the pattern
12586      * "^[#;%] [^ ]* game file", and a PGN tag block.
12587      * A game that starts with one of the latter two patterns
12588      * will also have a move number 1, possibly
12589      * following a position diagram.
12590      * 5-4-02: Let's try being more lenient and allowing a game to
12591      * start with an unnumbered move.  Does that break anything?
12592      */
12593     cm = lastLoadGameStart = EndOfFile;
12594     while (gn > 0) {
12595         yyboardindex = forwardMostMove;
12596         cm = (ChessMove) Myylex();
12597         switch (cm) {
12598           case EndOfFile:
12599             if (cmailMsgLoaded) {
12600                 nCmailGames = CMAIL_MAX_GAMES - gn;
12601             } else {
12602                 Reset(TRUE, TRUE);
12603                 DisplayError(_("Game not found in file"), 0);
12604             }
12605             return FALSE;
12606
12607           case GNUChessGame:
12608           case XBoardGame:
12609             gn--;
12610             lastLoadGameStart = cm;
12611             break;
12612
12613           case MoveNumberOne:
12614             switch (lastLoadGameStart) {
12615               case GNUChessGame:
12616               case XBoardGame:
12617               case PGNTag:
12618                 break;
12619               case MoveNumberOne:
12620               case EndOfFile:
12621                 gn--;           /* count this game */
12622                 lastLoadGameStart = cm;
12623                 break;
12624               default:
12625                 /* impossible */
12626                 break;
12627             }
12628             break;
12629
12630           case PGNTag:
12631             switch (lastLoadGameStart) {
12632               case GNUChessGame:
12633               case PGNTag:
12634               case MoveNumberOne:
12635               case EndOfFile:
12636                 gn--;           /* count this game */
12637                 lastLoadGameStart = cm;
12638                 break;
12639               case XBoardGame:
12640                 lastLoadGameStart = cm; /* game counted already */
12641                 break;
12642               default:
12643                 /* impossible */
12644                 break;
12645             }
12646             if (gn > 0) {
12647                 do {
12648                     yyboardindex = forwardMostMove;
12649                     cm = (ChessMove) Myylex();
12650                 } while (cm == PGNTag || cm == Comment);
12651             }
12652             break;
12653
12654           case WhiteWins:
12655           case BlackWins:
12656           case GameIsDrawn:
12657             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12658                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12659                     != CMAIL_OLD_RESULT) {
12660                     nCmailResults ++ ;
12661                     cmailResult[  CMAIL_MAX_GAMES
12662                                 - gn - 1] = CMAIL_OLD_RESULT;
12663                 }
12664             }
12665             break;
12666
12667           case NormalMove:
12668           case FirstLeg:
12669             /* Only a NormalMove can be at the start of a game
12670              * without a position diagram. */
12671             if (lastLoadGameStart == EndOfFile ) {
12672               gn--;
12673               lastLoadGameStart = MoveNumberOne;
12674             }
12675             break;
12676
12677           default:
12678             break;
12679         }
12680     }
12681
12682     if (appData.debugMode)
12683       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12684
12685     if (cm == XBoardGame) {
12686         /* Skip any header junk before position diagram and/or move 1 */
12687         for (;;) {
12688             yyboardindex = forwardMostMove;
12689             cm = (ChessMove) Myylex();
12690
12691             if (cm == EndOfFile ||
12692                 cm == GNUChessGame || cm == XBoardGame) {
12693                 /* Empty game; pretend end-of-file and handle later */
12694                 cm = EndOfFile;
12695                 break;
12696             }
12697
12698             if (cm == MoveNumberOne || cm == PositionDiagram ||
12699                 cm == PGNTag || cm == Comment)
12700               break;
12701         }
12702     } else if (cm == GNUChessGame) {
12703         if (gameInfo.event != NULL) {
12704             free(gameInfo.event);
12705         }
12706         gameInfo.event = StrSave(yy_text);
12707     }
12708
12709     startedFromSetupPosition = FALSE;
12710     while (cm == PGNTag) {
12711         if (appData.debugMode)
12712           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12713         err = ParsePGNTag(yy_text, &gameInfo);
12714         if (!err) numPGNTags++;
12715
12716         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12717         if(gameInfo.variant != oldVariant) {
12718             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12719             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12720             InitPosition(TRUE);
12721             oldVariant = gameInfo.variant;
12722             if (appData.debugMode)
12723               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12724         }
12725
12726
12727         if (gameInfo.fen != NULL) {
12728           Board initial_position;
12729           startedFromSetupPosition = TRUE;
12730           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12731             Reset(TRUE, TRUE);
12732             DisplayError(_("Bad FEN position in file"), 0);
12733             return FALSE;
12734           }
12735           CopyBoard(boards[0], initial_position);
12736           if (blackPlaysFirst) {
12737             currentMove = forwardMostMove = backwardMostMove = 1;
12738             CopyBoard(boards[1], initial_position);
12739             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12740             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12741             timeRemaining[0][1] = whiteTimeRemaining;
12742             timeRemaining[1][1] = blackTimeRemaining;
12743             if (commentList[0] != NULL) {
12744               commentList[1] = commentList[0];
12745               commentList[0] = NULL;
12746             }
12747           } else {
12748             currentMove = forwardMostMove = backwardMostMove = 0;
12749           }
12750           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12751           {   int i;
12752               initialRulePlies = FENrulePlies;
12753               for( i=0; i< nrCastlingRights; i++ )
12754                   initialRights[i] = initial_position[CASTLING][i];
12755           }
12756           yyboardindex = forwardMostMove;
12757           free(gameInfo.fen);
12758           gameInfo.fen = NULL;
12759         }
12760
12761         yyboardindex = forwardMostMove;
12762         cm = (ChessMove) Myylex();
12763
12764         /* Handle comments interspersed among the tags */
12765         while (cm == Comment) {
12766             char *p;
12767             if (appData.debugMode)
12768               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12769             p = yy_text;
12770             AppendComment(currentMove, p, FALSE);
12771             yyboardindex = forwardMostMove;
12772             cm = (ChessMove) Myylex();
12773         }
12774     }
12775
12776     /* don't rely on existence of Event tag since if game was
12777      * pasted from clipboard the Event tag may not exist
12778      */
12779     if (numPGNTags > 0){
12780         char *tags;
12781         if (gameInfo.variant == VariantNormal) {
12782           VariantClass v = StringToVariant(gameInfo.event);
12783           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12784           if(v < VariantShogi) gameInfo.variant = v;
12785         }
12786         if (!matchMode) {
12787           if( appData.autoDisplayTags ) {
12788             tags = PGNTags(&gameInfo);
12789             TagsPopUp(tags, CmailMsg());
12790             free(tags);
12791           }
12792         }
12793     } else {
12794         /* Make something up, but don't display it now */
12795         SetGameInfo();
12796         TagsPopDown();
12797     }
12798
12799     if (cm == PositionDiagram) {
12800         int i, j;
12801         char *p;
12802         Board initial_position;
12803
12804         if (appData.debugMode)
12805           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12806
12807         if (!startedFromSetupPosition) {
12808             p = yy_text;
12809             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12810               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12811                 switch (*p) {
12812                   case '{':
12813                   case '[':
12814                   case '-':
12815                   case ' ':
12816                   case '\t':
12817                   case '\n':
12818                   case '\r':
12819                     break;
12820                   default:
12821                     initial_position[i][j++] = CharToPiece(*p);
12822                     break;
12823                 }
12824             while (*p == ' ' || *p == '\t' ||
12825                    *p == '\n' || *p == '\r') p++;
12826
12827             if (strncmp(p, "black", strlen("black"))==0)
12828               blackPlaysFirst = TRUE;
12829             else
12830               blackPlaysFirst = FALSE;
12831             startedFromSetupPosition = TRUE;
12832
12833             CopyBoard(boards[0], initial_position);
12834             if (blackPlaysFirst) {
12835                 currentMove = forwardMostMove = backwardMostMove = 1;
12836                 CopyBoard(boards[1], initial_position);
12837                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12838                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12839                 timeRemaining[0][1] = whiteTimeRemaining;
12840                 timeRemaining[1][1] = blackTimeRemaining;
12841                 if (commentList[0] != NULL) {
12842                     commentList[1] = commentList[0];
12843                     commentList[0] = NULL;
12844                 }
12845             } else {
12846                 currentMove = forwardMostMove = backwardMostMove = 0;
12847             }
12848         }
12849         yyboardindex = forwardMostMove;
12850         cm = (ChessMove) Myylex();
12851     }
12852
12853   if(!creatingBook) {
12854     if (first.pr == NoProc) {
12855         StartChessProgram(&first);
12856     }
12857     InitChessProgram(&first, FALSE);
12858     SendToProgram("force\n", &first);
12859     if (startedFromSetupPosition) {
12860         SendBoard(&first, forwardMostMove);
12861     if (appData.debugMode) {
12862         fprintf(debugFP, "Load Game\n");
12863     }
12864         DisplayBothClocks();
12865     }
12866   }
12867
12868     /* [HGM] server: flag to write setup moves in broadcast file as one */
12869     loadFlag = appData.suppressLoadMoves;
12870
12871     while (cm == Comment) {
12872         char *p;
12873         if (appData.debugMode)
12874           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12875         p = yy_text;
12876         AppendComment(currentMove, p, FALSE);
12877         yyboardindex = forwardMostMove;
12878         cm = (ChessMove) Myylex();
12879     }
12880
12881     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12882         cm == WhiteWins || cm == BlackWins ||
12883         cm == GameIsDrawn || cm == GameUnfinished) {
12884         DisplayMessage("", _("No moves in game"));
12885         if (cmailMsgLoaded) {
12886             if (appData.debugMode)
12887               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12888             ClearHighlights();
12889             flipView = FALSE;
12890         }
12891         DrawPosition(FALSE, boards[currentMove]);
12892         DisplayBothClocks();
12893         gameMode = EditGame;
12894         ModeHighlight();
12895         gameFileFP = NULL;
12896         cmailOldMove = 0;
12897         return TRUE;
12898     }
12899
12900     // [HGM] PV info: routine tests if comment empty
12901     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12902         DisplayComment(currentMove - 1, commentList[currentMove]);
12903     }
12904     if (!matchMode && appData.timeDelay != 0)
12905       DrawPosition(FALSE, boards[currentMove]);
12906
12907     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12908       programStats.ok_to_send = 1;
12909     }
12910
12911     /* if the first token after the PGN tags is a move
12912      * and not move number 1, retrieve it from the parser
12913      */
12914     if (cm != MoveNumberOne)
12915         LoadGameOneMove(cm);
12916
12917     /* load the remaining moves from the file */
12918     while (LoadGameOneMove(EndOfFile)) {
12919       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12920       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12921     }
12922
12923     /* rewind to the start of the game */
12924     currentMove = backwardMostMove;
12925
12926     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12927
12928     if (oldGameMode == AnalyzeFile) {
12929       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12930       AnalyzeFileEvent();
12931     } else
12932     if (oldGameMode == AnalyzeMode) {
12933       AnalyzeFileEvent();
12934     }
12935
12936     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12937         long int w, b; // [HGM] adjourn: restore saved clock times
12938         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12939         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12940             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12941             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12942         }
12943     }
12944
12945     if(creatingBook) return TRUE;
12946     if (!matchMode && pos > 0) {
12947         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12948     } else
12949     if (matchMode || appData.timeDelay == 0) {
12950       ToEndEvent();
12951     } else if (appData.timeDelay > 0) {
12952       AutoPlayGameLoop();
12953     }
12954
12955     if (appData.debugMode)
12956         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12957
12958     loadFlag = 0; /* [HGM] true game starts */
12959     return TRUE;
12960 }
12961
12962 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12963 int
12964 ReloadPosition (int offset)
12965 {
12966     int positionNumber = lastLoadPositionNumber + offset;
12967     if (lastLoadPositionFP == NULL) {
12968         DisplayError(_("No position has been loaded yet"), 0);
12969         return FALSE;
12970     }
12971     if (positionNumber <= 0) {
12972         DisplayError(_("Can't back up any further"), 0);
12973         return FALSE;
12974     }
12975     return LoadPosition(lastLoadPositionFP, positionNumber,
12976                         lastLoadPositionTitle);
12977 }
12978
12979 /* Load the nth position from the given file */
12980 int
12981 LoadPositionFromFile (char *filename, int n, char *title)
12982 {
12983     FILE *f;
12984     char buf[MSG_SIZ];
12985
12986     if (strcmp(filename, "-") == 0) {
12987         return LoadPosition(stdin, n, "stdin");
12988     } else {
12989         f = fopen(filename, "rb");
12990         if (f == NULL) {
12991             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12992             DisplayError(buf, errno);
12993             return FALSE;
12994         } else {
12995             return LoadPosition(f, n, title);
12996         }
12997     }
12998 }
12999
13000 /* Load the nth position from the given open file, and close it */
13001 int
13002 LoadPosition (FILE *f, int positionNumber, char *title)
13003 {
13004     char *p, line[MSG_SIZ];
13005     Board initial_position;
13006     int i, j, fenMode, pn;
13007
13008     if (gameMode == Training )
13009         SetTrainingModeOff();
13010
13011     if (gameMode != BeginningOfGame) {
13012         Reset(FALSE, TRUE);
13013     }
13014     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13015         fclose(lastLoadPositionFP);
13016     }
13017     if (positionNumber == 0) positionNumber = 1;
13018     lastLoadPositionFP = f;
13019     lastLoadPositionNumber = positionNumber;
13020     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13021     if (first.pr == NoProc && !appData.noChessProgram) {
13022       StartChessProgram(&first);
13023       InitChessProgram(&first, FALSE);
13024     }
13025     pn = positionNumber;
13026     if (positionNumber < 0) {
13027         /* Negative position number means to seek to that byte offset */
13028         if (fseek(f, -positionNumber, 0) == -1) {
13029             DisplayError(_("Can't seek on position file"), 0);
13030             return FALSE;
13031         };
13032         pn = 1;
13033     } else {
13034         if (fseek(f, 0, 0) == -1) {
13035             if (f == lastLoadPositionFP ?
13036                 positionNumber == lastLoadPositionNumber + 1 :
13037                 positionNumber == 1) {
13038                 pn = 1;
13039             } else {
13040                 DisplayError(_("Can't seek on position file"), 0);
13041                 return FALSE;
13042             }
13043         }
13044     }
13045     /* See if this file is FEN or old-style xboard */
13046     if (fgets(line, MSG_SIZ, f) == NULL) {
13047         DisplayError(_("Position not found in file"), 0);
13048         return FALSE;
13049     }
13050     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13051     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13052
13053     if (pn >= 2) {
13054         if (fenMode || line[0] == '#') pn--;
13055         while (pn > 0) {
13056             /* skip positions before number pn */
13057             if (fgets(line, MSG_SIZ, f) == NULL) {
13058                 Reset(TRUE, TRUE);
13059                 DisplayError(_("Position not found in file"), 0);
13060                 return FALSE;
13061             }
13062             if (fenMode || line[0] == '#') pn--;
13063         }
13064     }
13065
13066     if (fenMode) {
13067         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13068             DisplayError(_("Bad FEN position in file"), 0);
13069             return FALSE;
13070         }
13071     } else {
13072         (void) fgets(line, MSG_SIZ, f);
13073         (void) fgets(line, MSG_SIZ, f);
13074
13075         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13076             (void) fgets(line, MSG_SIZ, f);
13077             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13078                 if (*p == ' ')
13079                   continue;
13080                 initial_position[i][j++] = CharToPiece(*p);
13081             }
13082         }
13083
13084         blackPlaysFirst = FALSE;
13085         if (!feof(f)) {
13086             (void) fgets(line, MSG_SIZ, f);
13087             if (strncmp(line, "black", strlen("black"))==0)
13088               blackPlaysFirst = TRUE;
13089         }
13090     }
13091     startedFromSetupPosition = TRUE;
13092
13093     CopyBoard(boards[0], initial_position);
13094     if (blackPlaysFirst) {
13095         currentMove = forwardMostMove = backwardMostMove = 1;
13096         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13097         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13098         CopyBoard(boards[1], initial_position);
13099         DisplayMessage("", _("Black to play"));
13100     } else {
13101         currentMove = forwardMostMove = backwardMostMove = 0;
13102         DisplayMessage("", _("White to play"));
13103     }
13104     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13105     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13106         SendToProgram("force\n", &first);
13107         SendBoard(&first, forwardMostMove);
13108     }
13109     if (appData.debugMode) {
13110 int i, j;
13111   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13112   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13113         fprintf(debugFP, "Load Position\n");
13114     }
13115
13116     if (positionNumber > 1) {
13117       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13118         DisplayTitle(line);
13119     } else {
13120         DisplayTitle(title);
13121     }
13122     gameMode = EditGame;
13123     ModeHighlight();
13124     ResetClocks();
13125     timeRemaining[0][1] = whiteTimeRemaining;
13126     timeRemaining[1][1] = blackTimeRemaining;
13127     DrawPosition(FALSE, boards[currentMove]);
13128
13129     return TRUE;
13130 }
13131
13132
13133 void
13134 CopyPlayerNameIntoFileName (char **dest, char *src)
13135 {
13136     while (*src != NULLCHAR && *src != ',') {
13137         if (*src == ' ') {
13138             *(*dest)++ = '_';
13139             src++;
13140         } else {
13141             *(*dest)++ = *src++;
13142         }
13143     }
13144 }
13145
13146 char *
13147 DefaultFileName (char *ext)
13148 {
13149     static char def[MSG_SIZ];
13150     char *p;
13151
13152     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13153         p = def;
13154         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13155         *p++ = '-';
13156         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13157         *p++ = '.';
13158         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13159     } else {
13160         def[0] = NULLCHAR;
13161     }
13162     return def;
13163 }
13164
13165 /* Save the current game to the given file */
13166 int
13167 SaveGameToFile (char *filename, int append)
13168 {
13169     FILE *f;
13170     char buf[MSG_SIZ];
13171     int result, i, t,tot=0;
13172
13173     if (strcmp(filename, "-") == 0) {
13174         return SaveGame(stdout, 0, NULL);
13175     } else {
13176         for(i=0; i<10; i++) { // upto 10 tries
13177              f = fopen(filename, append ? "a" : "w");
13178              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13179              if(f || errno != 13) break;
13180              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13181              tot += t;
13182         }
13183         if (f == NULL) {
13184             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13185             DisplayError(buf, errno);
13186             return FALSE;
13187         } else {
13188             safeStrCpy(buf, lastMsg, MSG_SIZ);
13189             DisplayMessage(_("Waiting for access to save file"), "");
13190             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13191             DisplayMessage(_("Saving game"), "");
13192             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13193             result = SaveGame(f, 0, NULL);
13194             DisplayMessage(buf, "");
13195             return result;
13196         }
13197     }
13198 }
13199
13200 char *
13201 SavePart (char *str)
13202 {
13203     static char buf[MSG_SIZ];
13204     char *p;
13205
13206     p = strchr(str, ' ');
13207     if (p == NULL) return str;
13208     strncpy(buf, str, p - str);
13209     buf[p - str] = NULLCHAR;
13210     return buf;
13211 }
13212
13213 #define PGN_MAX_LINE 75
13214
13215 #define PGN_SIDE_WHITE  0
13216 #define PGN_SIDE_BLACK  1
13217
13218 static int
13219 FindFirstMoveOutOfBook (int side)
13220 {
13221     int result = -1;
13222
13223     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13224         int index = backwardMostMove;
13225         int has_book_hit = 0;
13226
13227         if( (index % 2) != side ) {
13228             index++;
13229         }
13230
13231         while( index < forwardMostMove ) {
13232             /* Check to see if engine is in book */
13233             int depth = pvInfoList[index].depth;
13234             int score = pvInfoList[index].score;
13235             int in_book = 0;
13236
13237             if( depth <= 2 ) {
13238                 in_book = 1;
13239             }
13240             else if( score == 0 && depth == 63 ) {
13241                 in_book = 1; /* Zappa */
13242             }
13243             else if( score == 2 && depth == 99 ) {
13244                 in_book = 1; /* Abrok */
13245             }
13246
13247             has_book_hit += in_book;
13248
13249             if( ! in_book ) {
13250                 result = index;
13251
13252                 break;
13253             }
13254
13255             index += 2;
13256         }
13257     }
13258
13259     return result;
13260 }
13261
13262 void
13263 GetOutOfBookInfo (char * buf)
13264 {
13265     int oob[2];
13266     int i;
13267     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13268
13269     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13270     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13271
13272     *buf = '\0';
13273
13274     if( oob[0] >= 0 || oob[1] >= 0 ) {
13275         for( i=0; i<2; i++ ) {
13276             int idx = oob[i];
13277
13278             if( idx >= 0 ) {
13279                 if( i > 0 && oob[0] >= 0 ) {
13280                     strcat( buf, "   " );
13281                 }
13282
13283                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13284                 sprintf( buf+strlen(buf), "%s%.2f",
13285                     pvInfoList[idx].score >= 0 ? "+" : "",
13286                     pvInfoList[idx].score / 100.0 );
13287             }
13288         }
13289     }
13290 }
13291
13292 /* Save game in PGN style and close the file */
13293 int
13294 SaveGamePGN (FILE *f)
13295 {
13296     int i, offset, linelen, newblock;
13297 //    char *movetext;
13298     char numtext[32];
13299     int movelen, numlen, blank;
13300     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13301
13302     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13303
13304     PrintPGNTags(f, &gameInfo);
13305
13306     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13307
13308     if (backwardMostMove > 0 || startedFromSetupPosition) {
13309         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13310         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13311         fprintf(f, "\n{--------------\n");
13312         PrintPosition(f, backwardMostMove);
13313         fprintf(f, "--------------}\n");
13314         free(fen);
13315     }
13316     else {
13317         /* [AS] Out of book annotation */
13318         if( appData.saveOutOfBookInfo ) {
13319             char buf[64];
13320
13321             GetOutOfBookInfo( buf );
13322
13323             if( buf[0] != '\0' ) {
13324                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13325             }
13326         }
13327
13328         fprintf(f, "\n");
13329     }
13330
13331     i = backwardMostMove;
13332     linelen = 0;
13333     newblock = TRUE;
13334
13335     while (i < forwardMostMove) {
13336         /* Print comments preceding this move */
13337         if (commentList[i] != NULL) {
13338             if (linelen > 0) fprintf(f, "\n");
13339             fprintf(f, "%s", commentList[i]);
13340             linelen = 0;
13341             newblock = TRUE;
13342         }
13343
13344         /* Format move number */
13345         if ((i % 2) == 0)
13346           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13347         else
13348           if (newblock)
13349             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13350           else
13351             numtext[0] = NULLCHAR;
13352
13353         numlen = strlen(numtext);
13354         newblock = FALSE;
13355
13356         /* Print move number */
13357         blank = linelen > 0 && numlen > 0;
13358         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13359             fprintf(f, "\n");
13360             linelen = 0;
13361             blank = 0;
13362         }
13363         if (blank) {
13364             fprintf(f, " ");
13365             linelen++;
13366         }
13367         fprintf(f, "%s", numtext);
13368         linelen += numlen;
13369
13370         /* Get move */
13371         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13372         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13373
13374         /* Print move */
13375         blank = linelen > 0 && movelen > 0;
13376         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13377             fprintf(f, "\n");
13378             linelen = 0;
13379             blank = 0;
13380         }
13381         if (blank) {
13382             fprintf(f, " ");
13383             linelen++;
13384         }
13385         fprintf(f, "%s", move_buffer);
13386         linelen += movelen;
13387
13388         /* [AS] Add PV info if present */
13389         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13390             /* [HGM] add time */
13391             char buf[MSG_SIZ]; int seconds;
13392
13393             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13394
13395             if( seconds <= 0)
13396               buf[0] = 0;
13397             else
13398               if( seconds < 30 )
13399                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13400               else
13401                 {
13402                   seconds = (seconds + 4)/10; // round to full seconds
13403                   if( seconds < 60 )
13404                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13405                   else
13406                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13407                 }
13408
13409             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13410                       pvInfoList[i].score >= 0 ? "+" : "",
13411                       pvInfoList[i].score / 100.0,
13412                       pvInfoList[i].depth,
13413                       buf );
13414
13415             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13416
13417             /* Print score/depth */
13418             blank = linelen > 0 && movelen > 0;
13419             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13420                 fprintf(f, "\n");
13421                 linelen = 0;
13422                 blank = 0;
13423             }
13424             if (blank) {
13425                 fprintf(f, " ");
13426                 linelen++;
13427             }
13428             fprintf(f, "%s", move_buffer);
13429             linelen += movelen;
13430         }
13431
13432         i++;
13433     }
13434
13435     /* Start a new line */
13436     if (linelen > 0) fprintf(f, "\n");
13437
13438     /* Print comments after last move */
13439     if (commentList[i] != NULL) {
13440         fprintf(f, "%s\n", commentList[i]);
13441     }
13442
13443     /* Print result */
13444     if (gameInfo.resultDetails != NULL &&
13445         gameInfo.resultDetails[0] != NULLCHAR) {
13446         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13447         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13448            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13449             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13450         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13451     } else {
13452         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13453     }
13454
13455     fclose(f);
13456     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13457     return TRUE;
13458 }
13459
13460 /* Save game in old style and close the file */
13461 int
13462 SaveGameOldStyle (FILE *f)
13463 {
13464     int i, offset;
13465     time_t tm;
13466
13467     tm = time((time_t *) NULL);
13468
13469     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13470     PrintOpponents(f);
13471
13472     if (backwardMostMove > 0 || startedFromSetupPosition) {
13473         fprintf(f, "\n[--------------\n");
13474         PrintPosition(f, backwardMostMove);
13475         fprintf(f, "--------------]\n");
13476     } else {
13477         fprintf(f, "\n");
13478     }
13479
13480     i = backwardMostMove;
13481     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13482
13483     while (i < forwardMostMove) {
13484         if (commentList[i] != NULL) {
13485             fprintf(f, "[%s]\n", commentList[i]);
13486         }
13487
13488         if ((i % 2) == 1) {
13489             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13490             i++;
13491         } else {
13492             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13493             i++;
13494             if (commentList[i] != NULL) {
13495                 fprintf(f, "\n");
13496                 continue;
13497             }
13498             if (i >= forwardMostMove) {
13499                 fprintf(f, "\n");
13500                 break;
13501             }
13502             fprintf(f, "%s\n", parseList[i]);
13503             i++;
13504         }
13505     }
13506
13507     if (commentList[i] != NULL) {
13508         fprintf(f, "[%s]\n", commentList[i]);
13509     }
13510
13511     /* This isn't really the old style, but it's close enough */
13512     if (gameInfo.resultDetails != NULL &&
13513         gameInfo.resultDetails[0] != NULLCHAR) {
13514         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13515                 gameInfo.resultDetails);
13516     } else {
13517         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13518     }
13519
13520     fclose(f);
13521     return TRUE;
13522 }
13523
13524 /* Save the current game to open file f and close the file */
13525 int
13526 SaveGame (FILE *f, int dummy, char *dummy2)
13527 {
13528     if (gameMode == EditPosition) EditPositionDone(TRUE);
13529     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13530     if (appData.oldSaveStyle)
13531       return SaveGameOldStyle(f);
13532     else
13533       return SaveGamePGN(f);
13534 }
13535
13536 /* Save the current position to the given file */
13537 int
13538 SavePositionToFile (char *filename)
13539 {
13540     FILE *f;
13541     char buf[MSG_SIZ];
13542
13543     if (strcmp(filename, "-") == 0) {
13544         return SavePosition(stdout, 0, NULL);
13545     } else {
13546         f = fopen(filename, "a");
13547         if (f == NULL) {
13548             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13549             DisplayError(buf, errno);
13550             return FALSE;
13551         } else {
13552             safeStrCpy(buf, lastMsg, MSG_SIZ);
13553             DisplayMessage(_("Waiting for access to save file"), "");
13554             flock(fileno(f), LOCK_EX); // [HGM] lock
13555             DisplayMessage(_("Saving position"), "");
13556             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13557             SavePosition(f, 0, NULL);
13558             DisplayMessage(buf, "");
13559             return TRUE;
13560         }
13561     }
13562 }
13563
13564 /* Save the current position to the given open file and close the file */
13565 int
13566 SavePosition (FILE *f, int dummy, char *dummy2)
13567 {
13568     time_t tm;
13569     char *fen;
13570
13571     if (gameMode == EditPosition) EditPositionDone(TRUE);
13572     if (appData.oldSaveStyle) {
13573         tm = time((time_t *) NULL);
13574
13575         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13576         PrintOpponents(f);
13577         fprintf(f, "[--------------\n");
13578         PrintPosition(f, currentMove);
13579         fprintf(f, "--------------]\n");
13580     } else {
13581         fen = PositionToFEN(currentMove, NULL, 1);
13582         fprintf(f, "%s\n", fen);
13583         free(fen);
13584     }
13585     fclose(f);
13586     return TRUE;
13587 }
13588
13589 void
13590 ReloadCmailMsgEvent (int unregister)
13591 {
13592 #if !WIN32
13593     static char *inFilename = NULL;
13594     static char *outFilename;
13595     int i;
13596     struct stat inbuf, outbuf;
13597     int status;
13598
13599     /* Any registered moves are unregistered if unregister is set, */
13600     /* i.e. invoked by the signal handler */
13601     if (unregister) {
13602         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13603             cmailMoveRegistered[i] = FALSE;
13604             if (cmailCommentList[i] != NULL) {
13605                 free(cmailCommentList[i]);
13606                 cmailCommentList[i] = NULL;
13607             }
13608         }
13609         nCmailMovesRegistered = 0;
13610     }
13611
13612     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13613         cmailResult[i] = CMAIL_NOT_RESULT;
13614     }
13615     nCmailResults = 0;
13616
13617     if (inFilename == NULL) {
13618         /* Because the filenames are static they only get malloced once  */
13619         /* and they never get freed                                      */
13620         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13621         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13622
13623         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13624         sprintf(outFilename, "%s.out", appData.cmailGameName);
13625     }
13626
13627     status = stat(outFilename, &outbuf);
13628     if (status < 0) {
13629         cmailMailedMove = FALSE;
13630     } else {
13631         status = stat(inFilename, &inbuf);
13632         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13633     }
13634
13635     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13636        counts the games, notes how each one terminated, etc.
13637
13638        It would be nice to remove this kludge and instead gather all
13639        the information while building the game list.  (And to keep it
13640        in the game list nodes instead of having a bunch of fixed-size
13641        parallel arrays.)  Note this will require getting each game's
13642        termination from the PGN tags, as the game list builder does
13643        not process the game moves.  --mann
13644        */
13645     cmailMsgLoaded = TRUE;
13646     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13647
13648     /* Load first game in the file or popup game menu */
13649     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13650
13651 #endif /* !WIN32 */
13652     return;
13653 }
13654
13655 int
13656 RegisterMove ()
13657 {
13658     FILE *f;
13659     char string[MSG_SIZ];
13660
13661     if (   cmailMailedMove
13662         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13663         return TRUE;            /* Allow free viewing  */
13664     }
13665
13666     /* Unregister move to ensure that we don't leave RegisterMove        */
13667     /* with the move registered when the conditions for registering no   */
13668     /* longer hold                                                       */
13669     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13670         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13671         nCmailMovesRegistered --;
13672
13673         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13674           {
13675               free(cmailCommentList[lastLoadGameNumber - 1]);
13676               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13677           }
13678     }
13679
13680     if (cmailOldMove == -1) {
13681         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13682         return FALSE;
13683     }
13684
13685     if (currentMove > cmailOldMove + 1) {
13686         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13687         return FALSE;
13688     }
13689
13690     if (currentMove < cmailOldMove) {
13691         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13692         return FALSE;
13693     }
13694
13695     if (forwardMostMove > currentMove) {
13696         /* Silently truncate extra moves */
13697         TruncateGame();
13698     }
13699
13700     if (   (currentMove == cmailOldMove + 1)
13701         || (   (currentMove == cmailOldMove)
13702             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13703                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13704         if (gameInfo.result != GameUnfinished) {
13705             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13706         }
13707
13708         if (commentList[currentMove] != NULL) {
13709             cmailCommentList[lastLoadGameNumber - 1]
13710               = StrSave(commentList[currentMove]);
13711         }
13712         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13713
13714         if (appData.debugMode)
13715           fprintf(debugFP, "Saving %s for game %d\n",
13716                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13717
13718         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13719
13720         f = fopen(string, "w");
13721         if (appData.oldSaveStyle) {
13722             SaveGameOldStyle(f); /* also closes the file */
13723
13724             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13725             f = fopen(string, "w");
13726             SavePosition(f, 0, NULL); /* also closes the file */
13727         } else {
13728             fprintf(f, "{--------------\n");
13729             PrintPosition(f, currentMove);
13730             fprintf(f, "--------------}\n\n");
13731
13732             SaveGame(f, 0, NULL); /* also closes the file*/
13733         }
13734
13735         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13736         nCmailMovesRegistered ++;
13737     } else if (nCmailGames == 1) {
13738         DisplayError(_("You have not made a move yet"), 0);
13739         return FALSE;
13740     }
13741
13742     return TRUE;
13743 }
13744
13745 void
13746 MailMoveEvent ()
13747 {
13748 #if !WIN32
13749     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13750     FILE *commandOutput;
13751     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13752     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13753     int nBuffers;
13754     int i;
13755     int archived;
13756     char *arcDir;
13757
13758     if (! cmailMsgLoaded) {
13759         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13760         return;
13761     }
13762
13763     if (nCmailGames == nCmailResults) {
13764         DisplayError(_("No unfinished games"), 0);
13765         return;
13766     }
13767
13768 #if CMAIL_PROHIBIT_REMAIL
13769     if (cmailMailedMove) {
13770       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);
13771         DisplayError(msg, 0);
13772         return;
13773     }
13774 #endif
13775
13776     if (! (cmailMailedMove || RegisterMove())) return;
13777
13778     if (   cmailMailedMove
13779         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13780       snprintf(string, MSG_SIZ, partCommandString,
13781                appData.debugMode ? " -v" : "", appData.cmailGameName);
13782         commandOutput = popen(string, "r");
13783
13784         if (commandOutput == NULL) {
13785             DisplayError(_("Failed to invoke cmail"), 0);
13786         } else {
13787             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13788                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13789             }
13790             if (nBuffers > 1) {
13791                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13792                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13793                 nBytes = MSG_SIZ - 1;
13794             } else {
13795                 (void) memcpy(msg, buffer, nBytes);
13796             }
13797             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13798
13799             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13800                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13801
13802                 archived = TRUE;
13803                 for (i = 0; i < nCmailGames; i ++) {
13804                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13805                         archived = FALSE;
13806                     }
13807                 }
13808                 if (   archived
13809                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13810                         != NULL)) {
13811                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13812                            arcDir,
13813                            appData.cmailGameName,
13814                            gameInfo.date);
13815                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13816                     cmailMsgLoaded = FALSE;
13817                 }
13818             }
13819
13820             DisplayInformation(msg);
13821             pclose(commandOutput);
13822         }
13823     } else {
13824         if ((*cmailMsg) != '\0') {
13825             DisplayInformation(cmailMsg);
13826         }
13827     }
13828
13829     return;
13830 #endif /* !WIN32 */
13831 }
13832
13833 char *
13834 CmailMsg ()
13835 {
13836 #if WIN32
13837     return NULL;
13838 #else
13839     int  prependComma = 0;
13840     char number[5];
13841     char string[MSG_SIZ];       /* Space for game-list */
13842     int  i;
13843
13844     if (!cmailMsgLoaded) return "";
13845
13846     if (cmailMailedMove) {
13847       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13848     } else {
13849         /* Create a list of games left */
13850       snprintf(string, MSG_SIZ, "[");
13851         for (i = 0; i < nCmailGames; i ++) {
13852             if (! (   cmailMoveRegistered[i]
13853                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13854                 if (prependComma) {
13855                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13856                 } else {
13857                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13858                     prependComma = 1;
13859                 }
13860
13861                 strcat(string, number);
13862             }
13863         }
13864         strcat(string, "]");
13865
13866         if (nCmailMovesRegistered + nCmailResults == 0) {
13867             switch (nCmailGames) {
13868               case 1:
13869                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13870                 break;
13871
13872               case 2:
13873                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13874                 break;
13875
13876               default:
13877                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13878                          nCmailGames);
13879                 break;
13880             }
13881         } else {
13882             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13883               case 1:
13884                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13885                          string);
13886                 break;
13887
13888               case 0:
13889                 if (nCmailResults == nCmailGames) {
13890                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13891                 } else {
13892                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13893                 }
13894                 break;
13895
13896               default:
13897                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13898                          string);
13899             }
13900         }
13901     }
13902     return cmailMsg;
13903 #endif /* WIN32 */
13904 }
13905
13906 void
13907 ResetGameEvent ()
13908 {
13909     if (gameMode == Training)
13910       SetTrainingModeOff();
13911
13912     Reset(TRUE, TRUE);
13913     cmailMsgLoaded = FALSE;
13914     if (appData.icsActive) {
13915       SendToICS(ics_prefix);
13916       SendToICS("refresh\n");
13917     }
13918 }
13919
13920 void
13921 ExitEvent (int status)
13922 {
13923     exiting++;
13924     if (exiting > 2) {
13925       /* Give up on clean exit */
13926       exit(status);
13927     }
13928     if (exiting > 1) {
13929       /* Keep trying for clean exit */
13930       return;
13931     }
13932
13933     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13934
13935     if (telnetISR != NULL) {
13936       RemoveInputSource(telnetISR);
13937     }
13938     if (icsPR != NoProc) {
13939       DestroyChildProcess(icsPR, TRUE);
13940     }
13941
13942     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13943     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13944
13945     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13946     /* make sure this other one finishes before killing it!                  */
13947     if(endingGame) { int count = 0;
13948         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13949         while(endingGame && count++ < 10) DoSleep(1);
13950         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13951     }
13952
13953     /* Kill off chess programs */
13954     if (first.pr != NoProc) {
13955         ExitAnalyzeMode();
13956
13957         DoSleep( appData.delayBeforeQuit );
13958         SendToProgram("quit\n", &first);
13959         DoSleep( appData.delayAfterQuit );
13960         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13961     }
13962     if (second.pr != NoProc) {
13963         DoSleep( appData.delayBeforeQuit );
13964         SendToProgram("quit\n", &second);
13965         DoSleep( appData.delayAfterQuit );
13966         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13967     }
13968     if (first.isr != NULL) {
13969         RemoveInputSource(first.isr);
13970     }
13971     if (second.isr != NULL) {
13972         RemoveInputSource(second.isr);
13973     }
13974
13975     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13976     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13977
13978     ShutDownFrontEnd();
13979     exit(status);
13980 }
13981
13982 void
13983 PauseEngine (ChessProgramState *cps)
13984 {
13985     SendToProgram("pause\n", cps);
13986     cps->pause = 2;
13987 }
13988
13989 void
13990 UnPauseEngine (ChessProgramState *cps)
13991 {
13992     SendToProgram("resume\n", cps);
13993     cps->pause = 1;
13994 }
13995
13996 void
13997 PauseEvent ()
13998 {
13999     if (appData.debugMode)
14000         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14001     if (pausing) {
14002         pausing = FALSE;
14003         ModeHighlight();
14004         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14005             StartClocks();
14006             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14007                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14008                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14009             }
14010             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14011             HandleMachineMove(stashedInputMove, stalledEngine);
14012             stalledEngine = NULL;
14013             return;
14014         }
14015         if (gameMode == MachinePlaysWhite ||
14016             gameMode == TwoMachinesPlay   ||
14017             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14018             if(first.pause)  UnPauseEngine(&first);
14019             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14020             if(second.pause) UnPauseEngine(&second);
14021             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14022             StartClocks();
14023         } else {
14024             DisplayBothClocks();
14025         }
14026         if (gameMode == PlayFromGameFile) {
14027             if (appData.timeDelay >= 0)
14028                 AutoPlayGameLoop();
14029         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14030             Reset(FALSE, TRUE);
14031             SendToICS(ics_prefix);
14032             SendToICS("refresh\n");
14033         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14034             ForwardInner(forwardMostMove);
14035         }
14036         pauseExamInvalid = FALSE;
14037     } else {
14038         switch (gameMode) {
14039           default:
14040             return;
14041           case IcsExamining:
14042             pauseExamForwardMostMove = forwardMostMove;
14043             pauseExamInvalid = FALSE;
14044             /* fall through */
14045           case IcsObserving:
14046           case IcsPlayingWhite:
14047           case IcsPlayingBlack:
14048             pausing = TRUE;
14049             ModeHighlight();
14050             return;
14051           case PlayFromGameFile:
14052             (void) StopLoadGameTimer();
14053             pausing = TRUE;
14054             ModeHighlight();
14055             break;
14056           case BeginningOfGame:
14057             if (appData.icsActive) return;
14058             /* else fall through */
14059           case MachinePlaysWhite:
14060           case MachinePlaysBlack:
14061           case TwoMachinesPlay:
14062             if (forwardMostMove == 0)
14063               return;           /* don't pause if no one has moved */
14064             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14065                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14066                 if(onMove->pause) {           // thinking engine can be paused
14067                     PauseEngine(onMove);      // do it
14068                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14069                         PauseEngine(onMove->other);
14070                     else
14071                         SendToProgram("easy\n", onMove->other);
14072                     StopClocks();
14073                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14074             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14075                 if(first.pause) {
14076                     PauseEngine(&first);
14077                     StopClocks();
14078                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14079             } else { // human on move, pause pondering by either method
14080                 if(first.pause)
14081                     PauseEngine(&first);
14082                 else if(appData.ponderNextMove)
14083                     SendToProgram("easy\n", &first);
14084                 StopClocks();
14085             }
14086             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14087           case AnalyzeMode:
14088             pausing = TRUE;
14089             ModeHighlight();
14090             break;
14091         }
14092     }
14093 }
14094
14095 void
14096 EditCommentEvent ()
14097 {
14098     char title[MSG_SIZ];
14099
14100     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14101       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14102     } else {
14103       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14104                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14105                parseList[currentMove - 1]);
14106     }
14107
14108     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14109 }
14110
14111
14112 void
14113 EditTagsEvent ()
14114 {
14115     char *tags = PGNTags(&gameInfo);
14116     bookUp = FALSE;
14117     EditTagsPopUp(tags, NULL);
14118     free(tags);
14119 }
14120
14121 void
14122 ToggleSecond ()
14123 {
14124   if(second.analyzing) {
14125     SendToProgram("exit\n", &second);
14126     second.analyzing = FALSE;
14127   } else {
14128     if (second.pr == NoProc) StartChessProgram(&second);
14129     InitChessProgram(&second, FALSE);
14130     FeedMovesToProgram(&second, currentMove);
14131
14132     SendToProgram("analyze\n", &second);
14133     second.analyzing = TRUE;
14134   }
14135 }
14136
14137 /* Toggle ShowThinking */
14138 void
14139 ToggleShowThinking()
14140 {
14141   appData.showThinking = !appData.showThinking;
14142   ShowThinkingEvent();
14143 }
14144
14145 int
14146 AnalyzeModeEvent ()
14147 {
14148     char buf[MSG_SIZ];
14149
14150     if (!first.analysisSupport) {
14151       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14152       DisplayError(buf, 0);
14153       return 0;
14154     }
14155     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14156     if (appData.icsActive) {
14157         if (gameMode != IcsObserving) {
14158           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14159             DisplayError(buf, 0);
14160             /* secure check */
14161             if (appData.icsEngineAnalyze) {
14162                 if (appData.debugMode)
14163                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14164                 ExitAnalyzeMode();
14165                 ModeHighlight();
14166             }
14167             return 0;
14168         }
14169         /* if enable, user wants to disable icsEngineAnalyze */
14170         if (appData.icsEngineAnalyze) {
14171                 ExitAnalyzeMode();
14172                 ModeHighlight();
14173                 return 0;
14174         }
14175         appData.icsEngineAnalyze = TRUE;
14176         if (appData.debugMode)
14177             fprintf(debugFP, "ICS engine analyze starting... \n");
14178     }
14179
14180     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14181     if (appData.noChessProgram || gameMode == AnalyzeMode)
14182       return 0;
14183
14184     if (gameMode != AnalyzeFile) {
14185         if (!appData.icsEngineAnalyze) {
14186                EditGameEvent();
14187                if (gameMode != EditGame) return 0;
14188         }
14189         if (!appData.showThinking) ToggleShowThinking();
14190         ResurrectChessProgram();
14191         SendToProgram("analyze\n", &first);
14192         first.analyzing = TRUE;
14193         /*first.maybeThinking = TRUE;*/
14194         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14195         EngineOutputPopUp();
14196     }
14197     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14198     pausing = FALSE;
14199     ModeHighlight();
14200     SetGameInfo();
14201
14202     StartAnalysisClock();
14203     GetTimeMark(&lastNodeCountTime);
14204     lastNodeCount = 0;
14205     return 1;
14206 }
14207
14208 void
14209 AnalyzeFileEvent ()
14210 {
14211     if (appData.noChessProgram || gameMode == AnalyzeFile)
14212       return;
14213
14214     if (!first.analysisSupport) {
14215       char buf[MSG_SIZ];
14216       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14217       DisplayError(buf, 0);
14218       return;
14219     }
14220
14221     if (gameMode != AnalyzeMode) {
14222         keepInfo = 1; // mere annotating should not alter PGN tags
14223         EditGameEvent();
14224         keepInfo = 0;
14225         if (gameMode != EditGame) return;
14226         if (!appData.showThinking) ToggleShowThinking();
14227         ResurrectChessProgram();
14228         SendToProgram("analyze\n", &first);
14229         first.analyzing = TRUE;
14230         /*first.maybeThinking = TRUE;*/
14231         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14232         EngineOutputPopUp();
14233     }
14234     gameMode = AnalyzeFile;
14235     pausing = FALSE;
14236     ModeHighlight();
14237
14238     StartAnalysisClock();
14239     GetTimeMark(&lastNodeCountTime);
14240     lastNodeCount = 0;
14241     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14242     AnalysisPeriodicEvent(1);
14243 }
14244
14245 void
14246 MachineWhiteEvent ()
14247 {
14248     char buf[MSG_SIZ];
14249     char *bookHit = NULL;
14250
14251     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14252       return;
14253
14254
14255     if (gameMode == PlayFromGameFile ||
14256         gameMode == TwoMachinesPlay  ||
14257         gameMode == Training         ||
14258         gameMode == AnalyzeMode      ||
14259         gameMode == EndOfGame)
14260         EditGameEvent();
14261
14262     if (gameMode == EditPosition)
14263         EditPositionDone(TRUE);
14264
14265     if (!WhiteOnMove(currentMove)) {
14266         DisplayError(_("It is not White's turn"), 0);
14267         return;
14268     }
14269
14270     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14271       ExitAnalyzeMode();
14272
14273     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14274         gameMode == AnalyzeFile)
14275         TruncateGame();
14276
14277     ResurrectChessProgram();    /* in case it isn't running */
14278     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14279         gameMode = MachinePlaysWhite;
14280         ResetClocks();
14281     } else
14282     gameMode = MachinePlaysWhite;
14283     pausing = FALSE;
14284     ModeHighlight();
14285     SetGameInfo();
14286     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14287     DisplayTitle(buf);
14288     if (first.sendName) {
14289       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14290       SendToProgram(buf, &first);
14291     }
14292     if (first.sendTime) {
14293       if (first.useColors) {
14294         SendToProgram("black\n", &first); /*gnu kludge*/
14295       }
14296       SendTimeRemaining(&first, TRUE);
14297     }
14298     if (first.useColors) {
14299       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14300     }
14301     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14302     SetMachineThinkingEnables();
14303     first.maybeThinking = TRUE;
14304     StartClocks();
14305     firstMove = FALSE;
14306
14307     if (appData.autoFlipView && !flipView) {
14308       flipView = !flipView;
14309       DrawPosition(FALSE, NULL);
14310       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14311     }
14312
14313     if(bookHit) { // [HGM] book: simulate book reply
14314         static char bookMove[MSG_SIZ]; // a bit generous?
14315
14316         programStats.nodes = programStats.depth = programStats.time =
14317         programStats.score = programStats.got_only_move = 0;
14318         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14319
14320         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14321         strcat(bookMove, bookHit);
14322         HandleMachineMove(bookMove, &first);
14323     }
14324 }
14325
14326 void
14327 MachineBlackEvent ()
14328 {
14329   char buf[MSG_SIZ];
14330   char *bookHit = NULL;
14331
14332     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14333         return;
14334
14335
14336     if (gameMode == PlayFromGameFile ||
14337         gameMode == TwoMachinesPlay  ||
14338         gameMode == Training         ||
14339         gameMode == AnalyzeMode      ||
14340         gameMode == EndOfGame)
14341         EditGameEvent();
14342
14343     if (gameMode == EditPosition)
14344         EditPositionDone(TRUE);
14345
14346     if (WhiteOnMove(currentMove)) {
14347         DisplayError(_("It is not Black's turn"), 0);
14348         return;
14349     }
14350
14351     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14352       ExitAnalyzeMode();
14353
14354     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14355         gameMode == AnalyzeFile)
14356         TruncateGame();
14357
14358     ResurrectChessProgram();    /* in case it isn't running */
14359     gameMode = MachinePlaysBlack;
14360     pausing = FALSE;
14361     ModeHighlight();
14362     SetGameInfo();
14363     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14364     DisplayTitle(buf);
14365     if (first.sendName) {
14366       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14367       SendToProgram(buf, &first);
14368     }
14369     if (first.sendTime) {
14370       if (first.useColors) {
14371         SendToProgram("white\n", &first); /*gnu kludge*/
14372       }
14373       SendTimeRemaining(&first, FALSE);
14374     }
14375     if (first.useColors) {
14376       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14377     }
14378     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14379     SetMachineThinkingEnables();
14380     first.maybeThinking = TRUE;
14381     StartClocks();
14382
14383     if (appData.autoFlipView && flipView) {
14384       flipView = !flipView;
14385       DrawPosition(FALSE, NULL);
14386       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14387     }
14388     if(bookHit) { // [HGM] book: simulate book reply
14389         static char bookMove[MSG_SIZ]; // a bit generous?
14390
14391         programStats.nodes = programStats.depth = programStats.time =
14392         programStats.score = programStats.got_only_move = 0;
14393         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14394
14395         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14396         strcat(bookMove, bookHit);
14397         HandleMachineMove(bookMove, &first);
14398     }
14399 }
14400
14401
14402 void
14403 DisplayTwoMachinesTitle ()
14404 {
14405     char buf[MSG_SIZ];
14406     if (appData.matchGames > 0) {
14407         if(appData.tourneyFile[0]) {
14408           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14409                    gameInfo.white, _("vs."), gameInfo.black,
14410                    nextGame+1, appData.matchGames+1,
14411                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14412         } else
14413         if (first.twoMachinesColor[0] == 'w') {
14414           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14415                    gameInfo.white, _("vs."),  gameInfo.black,
14416                    first.matchWins, second.matchWins,
14417                    matchGame - 1 - (first.matchWins + second.matchWins));
14418         } else {
14419           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14420                    gameInfo.white, _("vs."), gameInfo.black,
14421                    second.matchWins, first.matchWins,
14422                    matchGame - 1 - (first.matchWins + second.matchWins));
14423         }
14424     } else {
14425       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14426     }
14427     DisplayTitle(buf);
14428 }
14429
14430 void
14431 SettingsMenuIfReady ()
14432 {
14433   if (second.lastPing != second.lastPong) {
14434     DisplayMessage("", _("Waiting for second chess program"));
14435     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14436     return;
14437   }
14438   ThawUI();
14439   DisplayMessage("", "");
14440   SettingsPopUp(&second);
14441 }
14442
14443 int
14444 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14445 {
14446     char buf[MSG_SIZ];
14447     if (cps->pr == NoProc) {
14448         StartChessProgram(cps);
14449         if (cps->protocolVersion == 1) {
14450           retry();
14451           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14452         } else {
14453           /* kludge: allow timeout for initial "feature" command */
14454           if(retry != TwoMachinesEventIfReady) FreezeUI();
14455           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14456           DisplayMessage("", buf);
14457           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14458         }
14459         return 1;
14460     }
14461     return 0;
14462 }
14463
14464 void
14465 TwoMachinesEvent P((void))
14466 {
14467     int i;
14468     char buf[MSG_SIZ];
14469     ChessProgramState *onmove;
14470     char *bookHit = NULL;
14471     static int stalling = 0;
14472     TimeMark now;
14473     long wait;
14474
14475     if (appData.noChessProgram) return;
14476
14477     switch (gameMode) {
14478       case TwoMachinesPlay:
14479         return;
14480       case MachinePlaysWhite:
14481       case MachinePlaysBlack:
14482         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14483             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14484             return;
14485         }
14486         /* fall through */
14487       case BeginningOfGame:
14488       case PlayFromGameFile:
14489       case EndOfGame:
14490         EditGameEvent();
14491         if (gameMode != EditGame) return;
14492         break;
14493       case EditPosition:
14494         EditPositionDone(TRUE);
14495         break;
14496       case AnalyzeMode:
14497       case AnalyzeFile:
14498         ExitAnalyzeMode();
14499         break;
14500       case EditGame:
14501       default:
14502         break;
14503     }
14504
14505 //    forwardMostMove = currentMove;
14506     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14507     startingEngine = TRUE;
14508
14509     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14510
14511     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14512     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14513       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14514       return;
14515     }
14516     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14517
14518     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14519                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14520         startingEngine = FALSE;
14521         DisplayError("second engine does not play this", 0);
14522         return;
14523     }
14524
14525     if(!stalling) {
14526       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14527       SendToProgram("force\n", &second);
14528       stalling = 1;
14529       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14530       return;
14531     }
14532     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14533     if(appData.matchPause>10000 || appData.matchPause<10)
14534                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14535     wait = SubtractTimeMarks(&now, &pauseStart);
14536     if(wait < appData.matchPause) {
14537         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14538         return;
14539     }
14540     // we are now committed to starting the game
14541     stalling = 0;
14542     DisplayMessage("", "");
14543     if (startedFromSetupPosition) {
14544         SendBoard(&second, backwardMostMove);
14545     if (appData.debugMode) {
14546         fprintf(debugFP, "Two Machines\n");
14547     }
14548     }
14549     for (i = backwardMostMove; i < forwardMostMove; i++) {
14550         SendMoveToProgram(i, &second);
14551     }
14552
14553     gameMode = TwoMachinesPlay;
14554     pausing = startingEngine = FALSE;
14555     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14556     SetGameInfo();
14557     DisplayTwoMachinesTitle();
14558     firstMove = TRUE;
14559     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14560         onmove = &first;
14561     } else {
14562         onmove = &second;
14563     }
14564     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14565     SendToProgram(first.computerString, &first);
14566     if (first.sendName) {
14567       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14568       SendToProgram(buf, &first);
14569     }
14570     SendToProgram(second.computerString, &second);
14571     if (second.sendName) {
14572       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14573       SendToProgram(buf, &second);
14574     }
14575
14576     ResetClocks();
14577     if (!first.sendTime || !second.sendTime) {
14578         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14579         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14580     }
14581     if (onmove->sendTime) {
14582       if (onmove->useColors) {
14583         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14584       }
14585       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14586     }
14587     if (onmove->useColors) {
14588       SendToProgram(onmove->twoMachinesColor, onmove);
14589     }
14590     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14591 //    SendToProgram("go\n", onmove);
14592     onmove->maybeThinking = TRUE;
14593     SetMachineThinkingEnables();
14594
14595     StartClocks();
14596
14597     if(bookHit) { // [HGM] book: simulate book reply
14598         static char bookMove[MSG_SIZ]; // a bit generous?
14599
14600         programStats.nodes = programStats.depth = programStats.time =
14601         programStats.score = programStats.got_only_move = 0;
14602         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14603
14604         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14605         strcat(bookMove, bookHit);
14606         savedMessage = bookMove; // args for deferred call
14607         savedState = onmove;
14608         ScheduleDelayedEvent(DeferredBookMove, 1);
14609     }
14610 }
14611
14612 void
14613 TrainingEvent ()
14614 {
14615     if (gameMode == Training) {
14616       SetTrainingModeOff();
14617       gameMode = PlayFromGameFile;
14618       DisplayMessage("", _("Training mode off"));
14619     } else {
14620       gameMode = Training;
14621       animateTraining = appData.animate;
14622
14623       /* make sure we are not already at the end of the game */
14624       if (currentMove < forwardMostMove) {
14625         SetTrainingModeOn();
14626         DisplayMessage("", _("Training mode on"));
14627       } else {
14628         gameMode = PlayFromGameFile;
14629         DisplayError(_("Already at end of game"), 0);
14630       }
14631     }
14632     ModeHighlight();
14633 }
14634
14635 void
14636 IcsClientEvent ()
14637 {
14638     if (!appData.icsActive) return;
14639     switch (gameMode) {
14640       case IcsPlayingWhite:
14641       case IcsPlayingBlack:
14642       case IcsObserving:
14643       case IcsIdle:
14644       case BeginningOfGame:
14645       case IcsExamining:
14646         return;
14647
14648       case EditGame:
14649         break;
14650
14651       case EditPosition:
14652         EditPositionDone(TRUE);
14653         break;
14654
14655       case AnalyzeMode:
14656       case AnalyzeFile:
14657         ExitAnalyzeMode();
14658         break;
14659
14660       default:
14661         EditGameEvent();
14662         break;
14663     }
14664
14665     gameMode = IcsIdle;
14666     ModeHighlight();
14667     return;
14668 }
14669
14670 void
14671 EditGameEvent ()
14672 {
14673     int i;
14674
14675     switch (gameMode) {
14676       case Training:
14677         SetTrainingModeOff();
14678         break;
14679       case MachinePlaysWhite:
14680       case MachinePlaysBlack:
14681       case BeginningOfGame:
14682         SendToProgram("force\n", &first);
14683         SetUserThinkingEnables();
14684         break;
14685       case PlayFromGameFile:
14686         (void) StopLoadGameTimer();
14687         if (gameFileFP != NULL) {
14688             gameFileFP = NULL;
14689         }
14690         break;
14691       case EditPosition:
14692         EditPositionDone(TRUE);
14693         break;
14694       case AnalyzeMode:
14695       case AnalyzeFile:
14696         ExitAnalyzeMode();
14697         SendToProgram("force\n", &first);
14698         break;
14699       case TwoMachinesPlay:
14700         GameEnds(EndOfFile, NULL, GE_PLAYER);
14701         ResurrectChessProgram();
14702         SetUserThinkingEnables();
14703         break;
14704       case EndOfGame:
14705         ResurrectChessProgram();
14706         break;
14707       case IcsPlayingBlack:
14708       case IcsPlayingWhite:
14709         DisplayError(_("Warning: You are still playing a game"), 0);
14710         break;
14711       case IcsObserving:
14712         DisplayError(_("Warning: You are still observing a game"), 0);
14713         break;
14714       case IcsExamining:
14715         DisplayError(_("Warning: You are still examining a game"), 0);
14716         break;
14717       case IcsIdle:
14718         break;
14719       case EditGame:
14720       default:
14721         return;
14722     }
14723
14724     pausing = FALSE;
14725     StopClocks();
14726     first.offeredDraw = second.offeredDraw = 0;
14727
14728     if (gameMode == PlayFromGameFile) {
14729         whiteTimeRemaining = timeRemaining[0][currentMove];
14730         blackTimeRemaining = timeRemaining[1][currentMove];
14731         DisplayTitle("");
14732     }
14733
14734     if (gameMode == MachinePlaysWhite ||
14735         gameMode == MachinePlaysBlack ||
14736         gameMode == TwoMachinesPlay ||
14737         gameMode == EndOfGame) {
14738         i = forwardMostMove;
14739         while (i > currentMove) {
14740             SendToProgram("undo\n", &first);
14741             i--;
14742         }
14743         if(!adjustedClock) {
14744         whiteTimeRemaining = timeRemaining[0][currentMove];
14745         blackTimeRemaining = timeRemaining[1][currentMove];
14746         DisplayBothClocks();
14747         }
14748         if (whiteFlag || blackFlag) {
14749             whiteFlag = blackFlag = 0;
14750         }
14751         DisplayTitle("");
14752     }
14753
14754     gameMode = EditGame;
14755     ModeHighlight();
14756     SetGameInfo();
14757 }
14758
14759
14760 void
14761 EditPositionEvent ()
14762 {
14763     if (gameMode == EditPosition) {
14764         EditGameEvent();
14765         return;
14766     }
14767
14768     EditGameEvent();
14769     if (gameMode != EditGame) return;
14770
14771     gameMode = EditPosition;
14772     ModeHighlight();
14773     SetGameInfo();
14774     if (currentMove > 0)
14775       CopyBoard(boards[0], boards[currentMove]);
14776
14777     blackPlaysFirst = !WhiteOnMove(currentMove);
14778     ResetClocks();
14779     currentMove = forwardMostMove = backwardMostMove = 0;
14780     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14781     DisplayMove(-1);
14782     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14783 }
14784
14785 void
14786 ExitAnalyzeMode ()
14787 {
14788     /* [DM] icsEngineAnalyze - possible call from other functions */
14789     if (appData.icsEngineAnalyze) {
14790         appData.icsEngineAnalyze = FALSE;
14791
14792         DisplayMessage("",_("Close ICS engine analyze..."));
14793     }
14794     if (first.analysisSupport && first.analyzing) {
14795       SendToBoth("exit\n");
14796       first.analyzing = second.analyzing = FALSE;
14797     }
14798     thinkOutput[0] = NULLCHAR;
14799 }
14800
14801 void
14802 EditPositionDone (Boolean fakeRights)
14803 {
14804     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14805
14806     startedFromSetupPosition = TRUE;
14807     InitChessProgram(&first, FALSE);
14808     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14809       boards[0][EP_STATUS] = EP_NONE;
14810       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14811       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14812         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14813         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14814       } else boards[0][CASTLING][2] = NoRights;
14815       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14816         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14817         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14818       } else boards[0][CASTLING][5] = NoRights;
14819       if(gameInfo.variant == VariantSChess) {
14820         int i;
14821         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14822           boards[0][VIRGIN][i] = 0;
14823           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14824           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14825         }
14826       }
14827     }
14828     SendToProgram("force\n", &first);
14829     if (blackPlaysFirst) {
14830         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14831         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14832         currentMove = forwardMostMove = backwardMostMove = 1;
14833         CopyBoard(boards[1], boards[0]);
14834     } else {
14835         currentMove = forwardMostMove = backwardMostMove = 0;
14836     }
14837     SendBoard(&first, forwardMostMove);
14838     if (appData.debugMode) {
14839         fprintf(debugFP, "EditPosDone\n");
14840     }
14841     DisplayTitle("");
14842     DisplayMessage("", "");
14843     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14844     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14845     gameMode = EditGame;
14846     ModeHighlight();
14847     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14848     ClearHighlights(); /* [AS] */
14849 }
14850
14851 /* Pause for `ms' milliseconds */
14852 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14853 void
14854 TimeDelay (long ms)
14855 {
14856     TimeMark m1, m2;
14857
14858     GetTimeMark(&m1);
14859     do {
14860         GetTimeMark(&m2);
14861     } while (SubtractTimeMarks(&m2, &m1) < ms);
14862 }
14863
14864 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14865 void
14866 SendMultiLineToICS (char *buf)
14867 {
14868     char temp[MSG_SIZ+1], *p;
14869     int len;
14870
14871     len = strlen(buf);
14872     if (len > MSG_SIZ)
14873       len = MSG_SIZ;
14874
14875     strncpy(temp, buf, len);
14876     temp[len] = 0;
14877
14878     p = temp;
14879     while (*p) {
14880         if (*p == '\n' || *p == '\r')
14881           *p = ' ';
14882         ++p;
14883     }
14884
14885     strcat(temp, "\n");
14886     SendToICS(temp);
14887     SendToPlayer(temp, strlen(temp));
14888 }
14889
14890 void
14891 SetWhiteToPlayEvent ()
14892 {
14893     if (gameMode == EditPosition) {
14894         blackPlaysFirst = FALSE;
14895         DisplayBothClocks();    /* works because currentMove is 0 */
14896     } else if (gameMode == IcsExamining) {
14897         SendToICS(ics_prefix);
14898         SendToICS("tomove white\n");
14899     }
14900 }
14901
14902 void
14903 SetBlackToPlayEvent ()
14904 {
14905     if (gameMode == EditPosition) {
14906         blackPlaysFirst = TRUE;
14907         currentMove = 1;        /* kludge */
14908         DisplayBothClocks();
14909         currentMove = 0;
14910     } else if (gameMode == IcsExamining) {
14911         SendToICS(ics_prefix);
14912         SendToICS("tomove black\n");
14913     }
14914 }
14915
14916 void
14917 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14918 {
14919     char buf[MSG_SIZ];
14920     ChessSquare piece = boards[0][y][x];
14921     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14922     static int lastVariant;
14923
14924     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14925
14926     switch (selection) {
14927       case ClearBoard:
14928         CopyBoard(currentBoard, boards[0]);
14929         CopyBoard(menuBoard, initialPosition);
14930         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14931             SendToICS(ics_prefix);
14932             SendToICS("bsetup clear\n");
14933         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14934             SendToICS(ics_prefix);
14935             SendToICS("clearboard\n");
14936         } else {
14937             int nonEmpty = 0;
14938             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14939                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14940                 for (y = 0; y < BOARD_HEIGHT; y++) {
14941                     if (gameMode == IcsExamining) {
14942                         if (boards[currentMove][y][x] != EmptySquare) {
14943                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14944                                     AAA + x, ONE + y);
14945                             SendToICS(buf);
14946                         }
14947                     } else {
14948                         if(boards[0][y][x] != p) nonEmpty++;
14949                         boards[0][y][x] = p;
14950                     }
14951                 }
14952                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14953             }
14954             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14955                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14956                     ChessSquare p = menuBoard[0][x];
14957                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14958                     p = menuBoard[BOARD_HEIGHT-1][x];
14959                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14960                 }
14961                 DisplayMessage("Clicking clock again restores position", "");
14962                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14963                 if(!nonEmpty) { // asked to clear an empty board
14964                     CopyBoard(boards[0], menuBoard);
14965                 } else
14966                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14967                     CopyBoard(boards[0], initialPosition);
14968                 } else
14969                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14970                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14971                     CopyBoard(boards[0], erasedBoard);
14972                 } else
14973                     CopyBoard(erasedBoard, currentBoard);
14974
14975             }
14976         }
14977         if (gameMode == EditPosition) {
14978             DrawPosition(FALSE, boards[0]);
14979         }
14980         break;
14981
14982       case WhitePlay:
14983         SetWhiteToPlayEvent();
14984         break;
14985
14986       case BlackPlay:
14987         SetBlackToPlayEvent();
14988         break;
14989
14990       case EmptySquare:
14991         if (gameMode == IcsExamining) {
14992             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14993             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14994             SendToICS(buf);
14995         } else {
14996             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14997                 if(x == BOARD_LEFT-2) {
14998                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14999                     boards[0][y][1] = 0;
15000                 } else
15001                 if(x == BOARD_RGHT+1) {
15002                     if(y >= gameInfo.holdingsSize) break;
15003                     boards[0][y][BOARD_WIDTH-2] = 0;
15004                 } else break;
15005             }
15006             boards[0][y][x] = EmptySquare;
15007             DrawPosition(FALSE, boards[0]);
15008         }
15009         break;
15010
15011       case PromotePiece:
15012         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15013            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15014             selection = (ChessSquare) (PROMOTED piece);
15015         } else if(piece == EmptySquare) selection = WhiteSilver;
15016         else selection = (ChessSquare)((int)piece - 1);
15017         goto defaultlabel;
15018
15019       case DemotePiece:
15020         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15021            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15022             selection = (ChessSquare) (DEMOTED piece);
15023         } else if(piece == EmptySquare) selection = BlackSilver;
15024         else selection = (ChessSquare)((int)piece + 1);
15025         goto defaultlabel;
15026
15027       case WhiteQueen:
15028       case BlackQueen:
15029         if(gameInfo.variant == VariantShatranj ||
15030            gameInfo.variant == VariantXiangqi  ||
15031            gameInfo.variant == VariantCourier  ||
15032            gameInfo.variant == VariantASEAN    ||
15033            gameInfo.variant == VariantMakruk     )
15034             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15035         goto defaultlabel;
15036
15037       case WhiteKing:
15038       case BlackKing:
15039         if(gameInfo.variant == VariantXiangqi)
15040             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15041         if(gameInfo.variant == VariantKnightmate)
15042             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15043       default:
15044         defaultlabel:
15045         if (gameMode == IcsExamining) {
15046             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15047             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15048                      PieceToChar(selection), AAA + x, ONE + y);
15049             SendToICS(buf);
15050         } else {
15051             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15052                 int n;
15053                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15054                     n = PieceToNumber(selection - BlackPawn);
15055                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15056                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15057                     boards[0][BOARD_HEIGHT-1-n][1]++;
15058                 } else
15059                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15060                     n = PieceToNumber(selection);
15061                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15062                     boards[0][n][BOARD_WIDTH-1] = selection;
15063                     boards[0][n][BOARD_WIDTH-2]++;
15064                 }
15065             } else
15066             boards[0][y][x] = selection;
15067             DrawPosition(TRUE, boards[0]);
15068             ClearHighlights();
15069             fromX = fromY = -1;
15070         }
15071         break;
15072     }
15073 }
15074
15075
15076 void
15077 DropMenuEvent (ChessSquare selection, int x, int y)
15078 {
15079     ChessMove moveType;
15080
15081     switch (gameMode) {
15082       case IcsPlayingWhite:
15083       case MachinePlaysBlack:
15084         if (!WhiteOnMove(currentMove)) {
15085             DisplayMoveError(_("It is Black's turn"));
15086             return;
15087         }
15088         moveType = WhiteDrop;
15089         break;
15090       case IcsPlayingBlack:
15091       case MachinePlaysWhite:
15092         if (WhiteOnMove(currentMove)) {
15093             DisplayMoveError(_("It is White's turn"));
15094             return;
15095         }
15096         moveType = BlackDrop;
15097         break;
15098       case EditGame:
15099         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15100         break;
15101       default:
15102         return;
15103     }
15104
15105     if (moveType == BlackDrop && selection < BlackPawn) {
15106       selection = (ChessSquare) ((int) selection
15107                                  + (int) BlackPawn - (int) WhitePawn);
15108     }
15109     if (boards[currentMove][y][x] != EmptySquare) {
15110         DisplayMoveError(_("That square is occupied"));
15111         return;
15112     }
15113
15114     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15115 }
15116
15117 void
15118 AcceptEvent ()
15119 {
15120     /* Accept a pending offer of any kind from opponent */
15121
15122     if (appData.icsActive) {
15123         SendToICS(ics_prefix);
15124         SendToICS("accept\n");
15125     } else if (cmailMsgLoaded) {
15126         if (currentMove == cmailOldMove &&
15127             commentList[cmailOldMove] != NULL &&
15128             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15129                    "Black offers a draw" : "White offers a draw")) {
15130             TruncateGame();
15131             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15132             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15133         } else {
15134             DisplayError(_("There is no pending offer on this move"), 0);
15135             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15136         }
15137     } else {
15138         /* Not used for offers from chess program */
15139     }
15140 }
15141
15142 void
15143 DeclineEvent ()
15144 {
15145     /* Decline a pending offer of any kind from opponent */
15146
15147     if (appData.icsActive) {
15148         SendToICS(ics_prefix);
15149         SendToICS("decline\n");
15150     } else if (cmailMsgLoaded) {
15151         if (currentMove == cmailOldMove &&
15152             commentList[cmailOldMove] != NULL &&
15153             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15154                    "Black offers a draw" : "White offers a draw")) {
15155 #ifdef NOTDEF
15156             AppendComment(cmailOldMove, "Draw declined", TRUE);
15157             DisplayComment(cmailOldMove - 1, "Draw declined");
15158 #endif /*NOTDEF*/
15159         } else {
15160             DisplayError(_("There is no pending offer on this move"), 0);
15161         }
15162     } else {
15163         /* Not used for offers from chess program */
15164     }
15165 }
15166
15167 void
15168 RematchEvent ()
15169 {
15170     /* Issue ICS rematch command */
15171     if (appData.icsActive) {
15172         SendToICS(ics_prefix);
15173         SendToICS("rematch\n");
15174     }
15175 }
15176
15177 void
15178 CallFlagEvent ()
15179 {
15180     /* Call your opponent's flag (claim a win on time) */
15181     if (appData.icsActive) {
15182         SendToICS(ics_prefix);
15183         SendToICS("flag\n");
15184     } else {
15185         switch (gameMode) {
15186           default:
15187             return;
15188           case MachinePlaysWhite:
15189             if (whiteFlag) {
15190                 if (blackFlag)
15191                   GameEnds(GameIsDrawn, "Both players ran out of time",
15192                            GE_PLAYER);
15193                 else
15194                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15195             } else {
15196                 DisplayError(_("Your opponent is not out of time"), 0);
15197             }
15198             break;
15199           case MachinePlaysBlack:
15200             if (blackFlag) {
15201                 if (whiteFlag)
15202                   GameEnds(GameIsDrawn, "Both players ran out of time",
15203                            GE_PLAYER);
15204                 else
15205                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15206             } else {
15207                 DisplayError(_("Your opponent is not out of time"), 0);
15208             }
15209             break;
15210         }
15211     }
15212 }
15213
15214 void
15215 ClockClick (int which)
15216 {       // [HGM] code moved to back-end from winboard.c
15217         if(which) { // black clock
15218           if (gameMode == EditPosition || gameMode == IcsExamining) {
15219             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15220             SetBlackToPlayEvent();
15221           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15222           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15223           } else if (shiftKey) {
15224             AdjustClock(which, -1);
15225           } else if (gameMode == IcsPlayingWhite ||
15226                      gameMode == MachinePlaysBlack) {
15227             CallFlagEvent();
15228           }
15229         } else { // white clock
15230           if (gameMode == EditPosition || gameMode == IcsExamining) {
15231             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15232             SetWhiteToPlayEvent();
15233           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15234           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15235           } else if (shiftKey) {
15236             AdjustClock(which, -1);
15237           } else if (gameMode == IcsPlayingBlack ||
15238                    gameMode == MachinePlaysWhite) {
15239             CallFlagEvent();
15240           }
15241         }
15242 }
15243
15244 void
15245 DrawEvent ()
15246 {
15247     /* Offer draw or accept pending draw offer from opponent */
15248
15249     if (appData.icsActive) {
15250         /* Note: tournament rules require draw offers to be
15251            made after you make your move but before you punch
15252            your clock.  Currently ICS doesn't let you do that;
15253            instead, you immediately punch your clock after making
15254            a move, but you can offer a draw at any time. */
15255
15256         SendToICS(ics_prefix);
15257         SendToICS("draw\n");
15258         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15259     } else if (cmailMsgLoaded) {
15260         if (currentMove == cmailOldMove &&
15261             commentList[cmailOldMove] != NULL &&
15262             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15263                    "Black offers a draw" : "White offers a draw")) {
15264             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15265             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15266         } else if (currentMove == cmailOldMove + 1) {
15267             char *offer = WhiteOnMove(cmailOldMove) ?
15268               "White offers a draw" : "Black offers a draw";
15269             AppendComment(currentMove, offer, TRUE);
15270             DisplayComment(currentMove - 1, offer);
15271             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15272         } else {
15273             DisplayError(_("You must make your move before offering a draw"), 0);
15274             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15275         }
15276     } else if (first.offeredDraw) {
15277         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15278     } else {
15279         if (first.sendDrawOffers) {
15280             SendToProgram("draw\n", &first);
15281             userOfferedDraw = TRUE;
15282         }
15283     }
15284 }
15285
15286 void
15287 AdjournEvent ()
15288 {
15289     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15290
15291     if (appData.icsActive) {
15292         SendToICS(ics_prefix);
15293         SendToICS("adjourn\n");
15294     } else {
15295         /* Currently GNU Chess doesn't offer or accept Adjourns */
15296     }
15297 }
15298
15299
15300 void
15301 AbortEvent ()
15302 {
15303     /* Offer Abort or accept pending Abort offer from opponent */
15304
15305     if (appData.icsActive) {
15306         SendToICS(ics_prefix);
15307         SendToICS("abort\n");
15308     } else {
15309         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15310     }
15311 }
15312
15313 void
15314 ResignEvent ()
15315 {
15316     /* Resign.  You can do this even if it's not your turn. */
15317
15318     if (appData.icsActive) {
15319         SendToICS(ics_prefix);
15320         SendToICS("resign\n");
15321     } else {
15322         switch (gameMode) {
15323           case MachinePlaysWhite:
15324             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15325             break;
15326           case MachinePlaysBlack:
15327             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15328             break;
15329           case EditGame:
15330             if (cmailMsgLoaded) {
15331                 TruncateGame();
15332                 if (WhiteOnMove(cmailOldMove)) {
15333                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15334                 } else {
15335                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15336                 }
15337                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15338             }
15339             break;
15340           default:
15341             break;
15342         }
15343     }
15344 }
15345
15346
15347 void
15348 StopObservingEvent ()
15349 {
15350     /* Stop observing current games */
15351     SendToICS(ics_prefix);
15352     SendToICS("unobserve\n");
15353 }
15354
15355 void
15356 StopExaminingEvent ()
15357 {
15358     /* Stop observing current game */
15359     SendToICS(ics_prefix);
15360     SendToICS("unexamine\n");
15361 }
15362
15363 void
15364 ForwardInner (int target)
15365 {
15366     int limit; int oldSeekGraphUp = seekGraphUp;
15367
15368     if (appData.debugMode)
15369         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15370                 target, currentMove, forwardMostMove);
15371
15372     if (gameMode == EditPosition)
15373       return;
15374
15375     seekGraphUp = FALSE;
15376     MarkTargetSquares(1);
15377
15378     if (gameMode == PlayFromGameFile && !pausing)
15379       PauseEvent();
15380
15381     if (gameMode == IcsExamining && pausing)
15382       limit = pauseExamForwardMostMove;
15383     else
15384       limit = forwardMostMove;
15385
15386     if (target > limit) target = limit;
15387
15388     if (target > 0 && moveList[target - 1][0]) {
15389         int fromX, fromY, toX, toY;
15390         toX = moveList[target - 1][2] - AAA;
15391         toY = moveList[target - 1][3] - ONE;
15392         if (moveList[target - 1][1] == '@') {
15393             if (appData.highlightLastMove) {
15394                 SetHighlights(-1, -1, toX, toY);
15395             }
15396         } else {
15397             fromX = moveList[target - 1][0] - AAA;
15398             fromY = moveList[target - 1][1] - ONE;
15399             if (target == currentMove + 1) {
15400                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15401             }
15402             if (appData.highlightLastMove) {
15403                 SetHighlights(fromX, fromY, toX, toY);
15404             }
15405         }
15406     }
15407     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15408         gameMode == Training || gameMode == PlayFromGameFile ||
15409         gameMode == AnalyzeFile) {
15410         while (currentMove < target) {
15411             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15412             SendMoveToProgram(currentMove++, &first);
15413         }
15414     } else {
15415         currentMove = target;
15416     }
15417
15418     if (gameMode == EditGame || gameMode == EndOfGame) {
15419         whiteTimeRemaining = timeRemaining[0][currentMove];
15420         blackTimeRemaining = timeRemaining[1][currentMove];
15421     }
15422     DisplayBothClocks();
15423     DisplayMove(currentMove - 1);
15424     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15425     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15426     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15427         DisplayComment(currentMove - 1, commentList[currentMove]);
15428     }
15429     ClearMap(); // [HGM] exclude: invalidate map
15430 }
15431
15432
15433 void
15434 ForwardEvent ()
15435 {
15436     if (gameMode == IcsExamining && !pausing) {
15437         SendToICS(ics_prefix);
15438         SendToICS("forward\n");
15439     } else {
15440         ForwardInner(currentMove + 1);
15441     }
15442 }
15443
15444 void
15445 ToEndEvent ()
15446 {
15447     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15448         /* to optimze, we temporarily turn off analysis mode while we feed
15449          * the remaining moves to the engine. Otherwise we get analysis output
15450          * after each move.
15451          */
15452         if (first.analysisSupport) {
15453           SendToProgram("exit\nforce\n", &first);
15454           first.analyzing = FALSE;
15455         }
15456     }
15457
15458     if (gameMode == IcsExamining && !pausing) {
15459         SendToICS(ics_prefix);
15460         SendToICS("forward 999999\n");
15461     } else {
15462         ForwardInner(forwardMostMove);
15463     }
15464
15465     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15466         /* we have fed all the moves, so reactivate analysis mode */
15467         SendToProgram("analyze\n", &first);
15468         first.analyzing = TRUE;
15469         /*first.maybeThinking = TRUE;*/
15470         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15471     }
15472 }
15473
15474 void
15475 BackwardInner (int target)
15476 {
15477     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15478
15479     if (appData.debugMode)
15480         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15481                 target, currentMove, forwardMostMove);
15482
15483     if (gameMode == EditPosition) return;
15484     seekGraphUp = FALSE;
15485     MarkTargetSquares(1);
15486     if (currentMove <= backwardMostMove) {
15487         ClearHighlights();
15488         DrawPosition(full_redraw, boards[currentMove]);
15489         return;
15490     }
15491     if (gameMode == PlayFromGameFile && !pausing)
15492       PauseEvent();
15493
15494     if (moveList[target][0]) {
15495         int fromX, fromY, toX, toY;
15496         toX = moveList[target][2] - AAA;
15497         toY = moveList[target][3] - ONE;
15498         if (moveList[target][1] == '@') {
15499             if (appData.highlightLastMove) {
15500                 SetHighlights(-1, -1, toX, toY);
15501             }
15502         } else {
15503             fromX = moveList[target][0] - AAA;
15504             fromY = moveList[target][1] - ONE;
15505             if (target == currentMove - 1) {
15506                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15507             }
15508             if (appData.highlightLastMove) {
15509                 SetHighlights(fromX, fromY, toX, toY);
15510             }
15511         }
15512     }
15513     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15514         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15515         while (currentMove > target) {
15516             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15517                 // null move cannot be undone. Reload program with move history before it.
15518                 int i;
15519                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15520                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15521                 }
15522                 SendBoard(&first, i);
15523               if(second.analyzing) SendBoard(&second, i);
15524                 for(currentMove=i; currentMove<target; currentMove++) {
15525                     SendMoveToProgram(currentMove, &first);
15526                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15527                 }
15528                 break;
15529             }
15530             SendToBoth("undo\n");
15531             currentMove--;
15532         }
15533     } else {
15534         currentMove = target;
15535     }
15536
15537     if (gameMode == EditGame || gameMode == EndOfGame) {
15538         whiteTimeRemaining = timeRemaining[0][currentMove];
15539         blackTimeRemaining = timeRemaining[1][currentMove];
15540     }
15541     DisplayBothClocks();
15542     DisplayMove(currentMove - 1);
15543     DrawPosition(full_redraw, boards[currentMove]);
15544     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15545     // [HGM] PV info: routine tests if comment empty
15546     DisplayComment(currentMove - 1, commentList[currentMove]);
15547     ClearMap(); // [HGM] exclude: invalidate map
15548 }
15549
15550 void
15551 BackwardEvent ()
15552 {
15553     if (gameMode == IcsExamining && !pausing) {
15554         SendToICS(ics_prefix);
15555         SendToICS("backward\n");
15556     } else {
15557         BackwardInner(currentMove - 1);
15558     }
15559 }
15560
15561 void
15562 ToStartEvent ()
15563 {
15564     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15565         /* to optimize, we temporarily turn off analysis mode while we undo
15566          * all the moves. Otherwise we get analysis output after each undo.
15567          */
15568         if (first.analysisSupport) {
15569           SendToProgram("exit\nforce\n", &first);
15570           first.analyzing = FALSE;
15571         }
15572     }
15573
15574     if (gameMode == IcsExamining && !pausing) {
15575         SendToICS(ics_prefix);
15576         SendToICS("backward 999999\n");
15577     } else {
15578         BackwardInner(backwardMostMove);
15579     }
15580
15581     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15582         /* we have fed all the moves, so reactivate analysis mode */
15583         SendToProgram("analyze\n", &first);
15584         first.analyzing = TRUE;
15585         /*first.maybeThinking = TRUE;*/
15586         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15587     }
15588 }
15589
15590 void
15591 ToNrEvent (int to)
15592 {
15593   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15594   if (to >= forwardMostMove) to = forwardMostMove;
15595   if (to <= backwardMostMove) to = backwardMostMove;
15596   if (to < currentMove) {
15597     BackwardInner(to);
15598   } else {
15599     ForwardInner(to);
15600   }
15601 }
15602
15603 void
15604 RevertEvent (Boolean annotate)
15605 {
15606     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15607         return;
15608     }
15609     if (gameMode != IcsExamining) {
15610         DisplayError(_("You are not examining a game"), 0);
15611         return;
15612     }
15613     if (pausing) {
15614         DisplayError(_("You can't revert while pausing"), 0);
15615         return;
15616     }
15617     SendToICS(ics_prefix);
15618     SendToICS("revert\n");
15619 }
15620
15621 void
15622 RetractMoveEvent ()
15623 {
15624     switch (gameMode) {
15625       case MachinePlaysWhite:
15626       case MachinePlaysBlack:
15627         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15628             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15629             return;
15630         }
15631         if (forwardMostMove < 2) return;
15632         currentMove = forwardMostMove = forwardMostMove - 2;
15633         whiteTimeRemaining = timeRemaining[0][currentMove];
15634         blackTimeRemaining = timeRemaining[1][currentMove];
15635         DisplayBothClocks();
15636         DisplayMove(currentMove - 1);
15637         ClearHighlights();/*!! could figure this out*/
15638         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15639         SendToProgram("remove\n", &first);
15640         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15641         break;
15642
15643       case BeginningOfGame:
15644       default:
15645         break;
15646
15647       case IcsPlayingWhite:
15648       case IcsPlayingBlack:
15649         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15650             SendToICS(ics_prefix);
15651             SendToICS("takeback 2\n");
15652         } else {
15653             SendToICS(ics_prefix);
15654             SendToICS("takeback 1\n");
15655         }
15656         break;
15657     }
15658 }
15659
15660 void
15661 MoveNowEvent ()
15662 {
15663     ChessProgramState *cps;
15664
15665     switch (gameMode) {
15666       case MachinePlaysWhite:
15667         if (!WhiteOnMove(forwardMostMove)) {
15668             DisplayError(_("It is your turn"), 0);
15669             return;
15670         }
15671         cps = &first;
15672         break;
15673       case MachinePlaysBlack:
15674         if (WhiteOnMove(forwardMostMove)) {
15675             DisplayError(_("It is your turn"), 0);
15676             return;
15677         }
15678         cps = &first;
15679         break;
15680       case TwoMachinesPlay:
15681         if (WhiteOnMove(forwardMostMove) ==
15682             (first.twoMachinesColor[0] == 'w')) {
15683             cps = &first;
15684         } else {
15685             cps = &second;
15686         }
15687         break;
15688       case BeginningOfGame:
15689       default:
15690         return;
15691     }
15692     SendToProgram("?\n", cps);
15693 }
15694
15695 void
15696 TruncateGameEvent ()
15697 {
15698     EditGameEvent();
15699     if (gameMode != EditGame) return;
15700     TruncateGame();
15701 }
15702
15703 void
15704 TruncateGame ()
15705 {
15706     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15707     if (forwardMostMove > currentMove) {
15708         if (gameInfo.resultDetails != NULL) {
15709             free(gameInfo.resultDetails);
15710             gameInfo.resultDetails = NULL;
15711             gameInfo.result = GameUnfinished;
15712         }
15713         forwardMostMove = currentMove;
15714         HistorySet(parseList, backwardMostMove, forwardMostMove,
15715                    currentMove-1);
15716     }
15717 }
15718
15719 void
15720 HintEvent ()
15721 {
15722     if (appData.noChessProgram) return;
15723     switch (gameMode) {
15724       case MachinePlaysWhite:
15725         if (WhiteOnMove(forwardMostMove)) {
15726             DisplayError(_("Wait until your turn."), 0);
15727             return;
15728         }
15729         break;
15730       case BeginningOfGame:
15731       case MachinePlaysBlack:
15732         if (!WhiteOnMove(forwardMostMove)) {
15733             DisplayError(_("Wait until your turn."), 0);
15734             return;
15735         }
15736         break;
15737       default:
15738         DisplayError(_("No hint available"), 0);
15739         return;
15740     }
15741     SendToProgram("hint\n", &first);
15742     hintRequested = TRUE;
15743 }
15744
15745 void
15746 CreateBookEvent ()
15747 {
15748     ListGame * lg = (ListGame *) gameList.head;
15749     FILE *f, *g;
15750     int nItem;
15751     static int secondTime = FALSE;
15752
15753     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15754         DisplayError(_("Game list not loaded or empty"), 0);
15755         return;
15756     }
15757
15758     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15759         fclose(g);
15760         secondTime++;
15761         DisplayNote(_("Book file exists! Try again for overwrite."));
15762         return;
15763     }
15764
15765     creatingBook = TRUE;
15766     secondTime = FALSE;
15767
15768     /* Get list size */
15769     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15770         LoadGame(f, nItem, "", TRUE);
15771         AddGameToBook(TRUE);
15772         lg = (ListGame *) lg->node.succ;
15773     }
15774
15775     creatingBook = FALSE;
15776     FlushBook();
15777 }
15778
15779 void
15780 BookEvent ()
15781 {
15782     if (appData.noChessProgram) return;
15783     switch (gameMode) {
15784       case MachinePlaysWhite:
15785         if (WhiteOnMove(forwardMostMove)) {
15786             DisplayError(_("Wait until your turn."), 0);
15787             return;
15788         }
15789         break;
15790       case BeginningOfGame:
15791       case MachinePlaysBlack:
15792         if (!WhiteOnMove(forwardMostMove)) {
15793             DisplayError(_("Wait until your turn."), 0);
15794             return;
15795         }
15796         break;
15797       case EditPosition:
15798         EditPositionDone(TRUE);
15799         break;
15800       case TwoMachinesPlay:
15801         return;
15802       default:
15803         break;
15804     }
15805     SendToProgram("bk\n", &first);
15806     bookOutput[0] = NULLCHAR;
15807     bookRequested = TRUE;
15808 }
15809
15810 void
15811 AboutGameEvent ()
15812 {
15813     char *tags = PGNTags(&gameInfo);
15814     TagsPopUp(tags, CmailMsg());
15815     free(tags);
15816 }
15817
15818 /* end button procedures */
15819
15820 void
15821 PrintPosition (FILE *fp, int move)
15822 {
15823     int i, j;
15824
15825     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15826         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15827             char c = PieceToChar(boards[move][i][j]);
15828             fputc(c == 'x' ? '.' : c, fp);
15829             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15830         }
15831     }
15832     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15833       fprintf(fp, "white to play\n");
15834     else
15835       fprintf(fp, "black to play\n");
15836 }
15837
15838 void
15839 PrintOpponents (FILE *fp)
15840 {
15841     if (gameInfo.white != NULL) {
15842         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15843     } else {
15844         fprintf(fp, "\n");
15845     }
15846 }
15847
15848 /* Find last component of program's own name, using some heuristics */
15849 void
15850 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15851 {
15852     char *p, *q, c;
15853     int local = (strcmp(host, "localhost") == 0);
15854     while (!local && (p = strchr(prog, ';')) != NULL) {
15855         p++;
15856         while (*p == ' ') p++;
15857         prog = p;
15858     }
15859     if (*prog == '"' || *prog == '\'') {
15860         q = strchr(prog + 1, *prog);
15861     } else {
15862         q = strchr(prog, ' ');
15863     }
15864     if (q == NULL) q = prog + strlen(prog);
15865     p = q;
15866     while (p >= prog && *p != '/' && *p != '\\') p--;
15867     p++;
15868     if(p == prog && *p == '"') p++;
15869     c = *q; *q = 0;
15870     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15871     memcpy(buf, p, q - p);
15872     buf[q - p] = NULLCHAR;
15873     if (!local) {
15874         strcat(buf, "@");
15875         strcat(buf, host);
15876     }
15877 }
15878
15879 char *
15880 TimeControlTagValue ()
15881 {
15882     char buf[MSG_SIZ];
15883     if (!appData.clockMode) {
15884       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15885     } else if (movesPerSession > 0) {
15886       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15887     } else if (timeIncrement == 0) {
15888       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15889     } else {
15890       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15891     }
15892     return StrSave(buf);
15893 }
15894
15895 void
15896 SetGameInfo ()
15897 {
15898     /* This routine is used only for certain modes */
15899     VariantClass v = gameInfo.variant;
15900     ChessMove r = GameUnfinished;
15901     char *p = NULL;
15902
15903     if(keepInfo) return;
15904
15905     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15906         r = gameInfo.result;
15907         p = gameInfo.resultDetails;
15908         gameInfo.resultDetails = NULL;
15909     }
15910     ClearGameInfo(&gameInfo);
15911     gameInfo.variant = v;
15912
15913     switch (gameMode) {
15914       case MachinePlaysWhite:
15915         gameInfo.event = StrSave( appData.pgnEventHeader );
15916         gameInfo.site = StrSave(HostName());
15917         gameInfo.date = PGNDate();
15918         gameInfo.round = StrSave("-");
15919         gameInfo.white = StrSave(first.tidy);
15920         gameInfo.black = StrSave(UserName());
15921         gameInfo.timeControl = TimeControlTagValue();
15922         break;
15923
15924       case MachinePlaysBlack:
15925         gameInfo.event = StrSave( appData.pgnEventHeader );
15926         gameInfo.site = StrSave(HostName());
15927         gameInfo.date = PGNDate();
15928         gameInfo.round = StrSave("-");
15929         gameInfo.white = StrSave(UserName());
15930         gameInfo.black = StrSave(first.tidy);
15931         gameInfo.timeControl = TimeControlTagValue();
15932         break;
15933
15934       case TwoMachinesPlay:
15935         gameInfo.event = StrSave( appData.pgnEventHeader );
15936         gameInfo.site = StrSave(HostName());
15937         gameInfo.date = PGNDate();
15938         if (roundNr > 0) {
15939             char buf[MSG_SIZ];
15940             snprintf(buf, MSG_SIZ, "%d", roundNr);
15941             gameInfo.round = StrSave(buf);
15942         } else {
15943             gameInfo.round = StrSave("-");
15944         }
15945         if (first.twoMachinesColor[0] == 'w') {
15946             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15947             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15948         } else {
15949             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15950             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15951         }
15952         gameInfo.timeControl = TimeControlTagValue();
15953         break;
15954
15955       case EditGame:
15956         gameInfo.event = StrSave("Edited game");
15957         gameInfo.site = StrSave(HostName());
15958         gameInfo.date = PGNDate();
15959         gameInfo.round = StrSave("-");
15960         gameInfo.white = StrSave("-");
15961         gameInfo.black = StrSave("-");
15962         gameInfo.result = r;
15963         gameInfo.resultDetails = p;
15964         break;
15965
15966       case EditPosition:
15967         gameInfo.event = StrSave("Edited position");
15968         gameInfo.site = StrSave(HostName());
15969         gameInfo.date = PGNDate();
15970         gameInfo.round = StrSave("-");
15971         gameInfo.white = StrSave("-");
15972         gameInfo.black = StrSave("-");
15973         break;
15974
15975       case IcsPlayingWhite:
15976       case IcsPlayingBlack:
15977       case IcsObserving:
15978       case IcsExamining:
15979         break;
15980
15981       case PlayFromGameFile:
15982         gameInfo.event = StrSave("Game from non-PGN file");
15983         gameInfo.site = StrSave(HostName());
15984         gameInfo.date = PGNDate();
15985         gameInfo.round = StrSave("-");
15986         gameInfo.white = StrSave("?");
15987         gameInfo.black = StrSave("?");
15988         break;
15989
15990       default:
15991         break;
15992     }
15993 }
15994
15995 void
15996 ReplaceComment (int index, char *text)
15997 {
15998     int len;
15999     char *p;
16000     float score;
16001
16002     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16003        pvInfoList[index-1].depth == len &&
16004        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16005        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16006     while (*text == '\n') text++;
16007     len = strlen(text);
16008     while (len > 0 && text[len - 1] == '\n') len--;
16009
16010     if (commentList[index] != NULL)
16011       free(commentList[index]);
16012
16013     if (len == 0) {
16014         commentList[index] = NULL;
16015         return;
16016     }
16017   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16018       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16019       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16020     commentList[index] = (char *) malloc(len + 2);
16021     strncpy(commentList[index], text, len);
16022     commentList[index][len] = '\n';
16023     commentList[index][len + 1] = NULLCHAR;
16024   } else {
16025     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16026     char *p;
16027     commentList[index] = (char *) malloc(len + 7);
16028     safeStrCpy(commentList[index], "{\n", 3);
16029     safeStrCpy(commentList[index]+2, text, len+1);
16030     commentList[index][len+2] = NULLCHAR;
16031     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16032     strcat(commentList[index], "\n}\n");
16033   }
16034 }
16035
16036 void
16037 CrushCRs (char *text)
16038 {
16039   char *p = text;
16040   char *q = text;
16041   char ch;
16042
16043   do {
16044     ch = *p++;
16045     if (ch == '\r') continue;
16046     *q++ = ch;
16047   } while (ch != '\0');
16048 }
16049
16050 void
16051 AppendComment (int index, char *text, Boolean addBraces)
16052 /* addBraces  tells if we should add {} */
16053 {
16054     int oldlen, len;
16055     char *old;
16056
16057 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16058     if(addBraces == 3) addBraces = 0; else // force appending literally
16059     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16060
16061     CrushCRs(text);
16062     while (*text == '\n') text++;
16063     len = strlen(text);
16064     while (len > 0 && text[len - 1] == '\n') len--;
16065     text[len] = NULLCHAR;
16066
16067     if (len == 0) return;
16068
16069     if (commentList[index] != NULL) {
16070       Boolean addClosingBrace = addBraces;
16071         old = commentList[index];
16072         oldlen = strlen(old);
16073         while(commentList[index][oldlen-1] ==  '\n')
16074           commentList[index][--oldlen] = NULLCHAR;
16075         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16076         safeStrCpy(commentList[index], old, oldlen + len + 6);
16077         free(old);
16078         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16079         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16080           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16081           while (*text == '\n') { text++; len--; }
16082           commentList[index][--oldlen] = NULLCHAR;
16083       }
16084         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16085         else          strcat(commentList[index], "\n");
16086         strcat(commentList[index], text);
16087         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16088         else          strcat(commentList[index], "\n");
16089     } else {
16090         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16091         if(addBraces)
16092           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16093         else commentList[index][0] = NULLCHAR;
16094         strcat(commentList[index], text);
16095         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16096         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16097     }
16098 }
16099
16100 static char *
16101 FindStr (char * text, char * sub_text)
16102 {
16103     char * result = strstr( text, sub_text );
16104
16105     if( result != NULL ) {
16106         result += strlen( sub_text );
16107     }
16108
16109     return result;
16110 }
16111
16112 /* [AS] Try to extract PV info from PGN comment */
16113 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16114 char *
16115 GetInfoFromComment (int index, char * text)
16116 {
16117     char * sep = text, *p;
16118
16119     if( text != NULL && index > 0 ) {
16120         int score = 0;
16121         int depth = 0;
16122         int time = -1, sec = 0, deci;
16123         char * s_eval = FindStr( text, "[%eval " );
16124         char * s_emt = FindStr( text, "[%emt " );
16125 #if 0
16126         if( s_eval != NULL || s_emt != NULL ) {
16127 #else
16128         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16129 #endif
16130             /* New style */
16131             char delim;
16132
16133             if( s_eval != NULL ) {
16134                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16135                     return text;
16136                 }
16137
16138                 if( delim != ']' ) {
16139                     return text;
16140                 }
16141             }
16142
16143             if( s_emt != NULL ) {
16144             }
16145                 return text;
16146         }
16147         else {
16148             /* We expect something like: [+|-]nnn.nn/dd */
16149             int score_lo = 0;
16150
16151             if(*text != '{') return text; // [HGM] braces: must be normal comment
16152
16153             sep = strchr( text, '/' );
16154             if( sep == NULL || sep < (text+4) ) {
16155                 return text;
16156             }
16157
16158             p = text;
16159             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16160             if(p[1] == '(') { // comment starts with PV
16161                p = strchr(p, ')'); // locate end of PV
16162                if(p == NULL || sep < p+5) return text;
16163                // at this point we have something like "{(.*) +0.23/6 ..."
16164                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16165                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16166                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16167             }
16168             time = -1; sec = -1; deci = -1;
16169             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16170                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16171                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16172                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16173                 return text;
16174             }
16175
16176             if( score_lo < 0 || score_lo >= 100 ) {
16177                 return text;
16178             }
16179
16180             if(sec >= 0) time = 600*time + 10*sec; else
16181             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16182
16183             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16184
16185             /* [HGM] PV time: now locate end of PV info */
16186             while( *++sep >= '0' && *sep <= '9'); // strip depth
16187             if(time >= 0)
16188             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16189             if(sec >= 0)
16190             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16191             if(deci >= 0)
16192             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16193             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16194         }
16195
16196         if( depth <= 0 ) {
16197             return text;
16198         }
16199
16200         if( time < 0 ) {
16201             time = -1;
16202         }
16203
16204         pvInfoList[index-1].depth = depth;
16205         pvInfoList[index-1].score = score;
16206         pvInfoList[index-1].time  = 10*time; // centi-sec
16207         if(*sep == '}') *sep = 0; else *--sep = '{';
16208         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16209     }
16210     return sep;
16211 }
16212
16213 void
16214 SendToProgram (char *message, ChessProgramState *cps)
16215 {
16216     int count, outCount, error;
16217     char buf[MSG_SIZ];
16218
16219     if (cps->pr == NoProc) return;
16220     Attention(cps);
16221
16222     if (appData.debugMode) {
16223         TimeMark now;
16224         GetTimeMark(&now);
16225         fprintf(debugFP, "%ld >%-6s: %s",
16226                 SubtractTimeMarks(&now, &programStartTime),
16227                 cps->which, message);
16228         if(serverFP)
16229             fprintf(serverFP, "%ld >%-6s: %s",
16230                 SubtractTimeMarks(&now, &programStartTime),
16231                 cps->which, message), fflush(serverFP);
16232     }
16233
16234     count = strlen(message);
16235     outCount = OutputToProcess(cps->pr, message, count, &error);
16236     if (outCount < count && !exiting
16237                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16238       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16239       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16240         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16241             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16242                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16243                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16244                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16245             } else {
16246                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16247                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16248                 gameInfo.result = res;
16249             }
16250             gameInfo.resultDetails = StrSave(buf);
16251         }
16252         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16253         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16254     }
16255 }
16256
16257 void
16258 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16259 {
16260     char *end_str;
16261     char buf[MSG_SIZ];
16262     ChessProgramState *cps = (ChessProgramState *)closure;
16263
16264     if (isr != cps->isr) return; /* Killed intentionally */
16265     if (count <= 0) {
16266         if (count == 0) {
16267             RemoveInputSource(cps->isr);
16268             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16269                     _(cps->which), cps->program);
16270             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16271             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16272                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16273                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16274                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16275                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16276                 } else {
16277                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16278                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16279                     gameInfo.result = res;
16280                 }
16281                 gameInfo.resultDetails = StrSave(buf);
16282             }
16283             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16284             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16285         } else {
16286             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16287                     _(cps->which), cps->program);
16288             RemoveInputSource(cps->isr);
16289
16290             /* [AS] Program is misbehaving badly... kill it */
16291             if( count == -2 ) {
16292                 DestroyChildProcess( cps->pr, 9 );
16293                 cps->pr = NoProc;
16294             }
16295
16296             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16297         }
16298         return;
16299     }
16300
16301     if ((end_str = strchr(message, '\r')) != NULL)
16302       *end_str = NULLCHAR;
16303     if ((end_str = strchr(message, '\n')) != NULL)
16304       *end_str = NULLCHAR;
16305
16306     if (appData.debugMode) {
16307         TimeMark now; int print = 1;
16308         char *quote = ""; char c; int i;
16309
16310         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16311                 char start = message[0];
16312                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16313                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16314                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16315                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16316                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16317                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16318                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16319                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16320                    sscanf(message, "hint: %c", &c)!=1 &&
16321                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16322                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16323                     print = (appData.engineComments >= 2);
16324                 }
16325                 message[0] = start; // restore original message
16326         }
16327         if(print) {
16328                 GetTimeMark(&now);
16329                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16330                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16331                         quote,
16332                         message);
16333                 if(serverFP)
16334                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16335                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16336                         quote,
16337                         message), fflush(serverFP);
16338         }
16339     }
16340
16341     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16342     if (appData.icsEngineAnalyze) {
16343         if (strstr(message, "whisper") != NULL ||
16344              strstr(message, "kibitz") != NULL ||
16345             strstr(message, "tellics") != NULL) return;
16346     }
16347
16348     HandleMachineMove(message, cps);
16349 }
16350
16351
16352 void
16353 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16354 {
16355     char buf[MSG_SIZ];
16356     int seconds;
16357
16358     if( timeControl_2 > 0 ) {
16359         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16360             tc = timeControl_2;
16361         }
16362     }
16363     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16364     inc /= cps->timeOdds;
16365     st  /= cps->timeOdds;
16366
16367     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16368
16369     if (st > 0) {
16370       /* Set exact time per move, normally using st command */
16371       if (cps->stKludge) {
16372         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16373         seconds = st % 60;
16374         if (seconds == 0) {
16375           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16376         } else {
16377           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16378         }
16379       } else {
16380         snprintf(buf, MSG_SIZ, "st %d\n", st);
16381       }
16382     } else {
16383       /* Set conventional or incremental time control, using level command */
16384       if (seconds == 0) {
16385         /* Note old gnuchess bug -- minutes:seconds used to not work.
16386            Fixed in later versions, but still avoid :seconds
16387            when seconds is 0. */
16388         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16389       } else {
16390         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16391                  seconds, inc/1000.);
16392       }
16393     }
16394     SendToProgram(buf, cps);
16395
16396     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16397     /* Orthogonally, limit search to given depth */
16398     if (sd > 0) {
16399       if (cps->sdKludge) {
16400         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16401       } else {
16402         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16403       }
16404       SendToProgram(buf, cps);
16405     }
16406
16407     if(cps->nps >= 0) { /* [HGM] nps */
16408         if(cps->supportsNPS == FALSE)
16409           cps->nps = -1; // don't use if engine explicitly says not supported!
16410         else {
16411           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16412           SendToProgram(buf, cps);
16413         }
16414     }
16415 }
16416
16417 ChessProgramState *
16418 WhitePlayer ()
16419 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16420 {
16421     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16422        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16423         return &second;
16424     return &first;
16425 }
16426
16427 void
16428 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16429 {
16430     char message[MSG_SIZ];
16431     long time, otime;
16432
16433     /* Note: this routine must be called when the clocks are stopped
16434        or when they have *just* been set or switched; otherwise
16435        it will be off by the time since the current tick started.
16436     */
16437     if (machineWhite) {
16438         time = whiteTimeRemaining / 10;
16439         otime = blackTimeRemaining / 10;
16440     } else {
16441         time = blackTimeRemaining / 10;
16442         otime = whiteTimeRemaining / 10;
16443     }
16444     /* [HGM] translate opponent's time by time-odds factor */
16445     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16446
16447     if (time <= 0) time = 1;
16448     if (otime <= 0) otime = 1;
16449
16450     snprintf(message, MSG_SIZ, "time %ld\n", time);
16451     SendToProgram(message, cps);
16452
16453     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16454     SendToProgram(message, cps);
16455 }
16456
16457 char *
16458 EngineDefinedVariant (ChessProgramState *cps, int n)
16459 {   // return name of n-th unknown variant that engine supports
16460     static char buf[MSG_SIZ];
16461     char *p, *s = cps->variants;
16462     if(!s) return NULL;
16463     do { // parse string from variants feature
16464       VariantClass v;
16465         p = strchr(s, ',');
16466         if(p) *p = NULLCHAR;
16467       v = StringToVariant(s);
16468       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16469         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16470             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16471         }
16472         if(p) *p++ = ',';
16473         if(n < 0) return buf;
16474     } while(s = p);
16475     return NULL;
16476 }
16477
16478 int
16479 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16480 {
16481   char buf[MSG_SIZ];
16482   int len = strlen(name);
16483   int val;
16484
16485   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16486     (*p) += len + 1;
16487     sscanf(*p, "%d", &val);
16488     *loc = (val != 0);
16489     while (**p && **p != ' ')
16490       (*p)++;
16491     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16492     SendToProgram(buf, cps);
16493     return TRUE;
16494   }
16495   return FALSE;
16496 }
16497
16498 int
16499 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16500 {
16501   char buf[MSG_SIZ];
16502   int len = strlen(name);
16503   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16504     (*p) += len + 1;
16505     sscanf(*p, "%d", loc);
16506     while (**p && **p != ' ') (*p)++;
16507     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16508     SendToProgram(buf, cps);
16509     return TRUE;
16510   }
16511   return FALSE;
16512 }
16513
16514 int
16515 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16516 {
16517   char buf[MSG_SIZ];
16518   int len = strlen(name);
16519   if (strncmp((*p), name, len) == 0
16520       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16521     (*p) += len + 2;
16522     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16523     sscanf(*p, "%[^\"]", *loc);
16524     while (**p && **p != '\"') (*p)++;
16525     if (**p == '\"') (*p)++;
16526     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16527     SendToProgram(buf, cps);
16528     return TRUE;
16529   }
16530   return FALSE;
16531 }
16532
16533 int
16534 ParseOption (Option *opt, ChessProgramState *cps)
16535 // [HGM] options: process the string that defines an engine option, and determine
16536 // name, type, default value, and allowed value range
16537 {
16538         char *p, *q, buf[MSG_SIZ];
16539         int n, min = (-1)<<31, max = 1<<31, def;
16540
16541         if(p = strstr(opt->name, " -spin ")) {
16542             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16543             if(max < min) max = min; // enforce consistency
16544             if(def < min) def = min;
16545             if(def > max) def = max;
16546             opt->value = def;
16547             opt->min = min;
16548             opt->max = max;
16549             opt->type = Spin;
16550         } else if((p = strstr(opt->name, " -slider "))) {
16551             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16552             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16553             if(max < min) max = min; // enforce consistency
16554             if(def < min) def = min;
16555             if(def > max) def = max;
16556             opt->value = def;
16557             opt->min = min;
16558             opt->max = max;
16559             opt->type = Spin; // Slider;
16560         } else if((p = strstr(opt->name, " -string "))) {
16561             opt->textValue = p+9;
16562             opt->type = TextBox;
16563         } else if((p = strstr(opt->name, " -file "))) {
16564             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16565             opt->textValue = p+7;
16566             opt->type = FileName; // FileName;
16567         } else if((p = strstr(opt->name, " -path "))) {
16568             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16569             opt->textValue = p+7;
16570             opt->type = PathName; // PathName;
16571         } else if(p = strstr(opt->name, " -check ")) {
16572             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16573             opt->value = (def != 0);
16574             opt->type = CheckBox;
16575         } else if(p = strstr(opt->name, " -combo ")) {
16576             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16577             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16578             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16579             opt->value = n = 0;
16580             while(q = StrStr(q, " /// ")) {
16581                 n++; *q = 0;    // count choices, and null-terminate each of them
16582                 q += 5;
16583                 if(*q == '*') { // remember default, which is marked with * prefix
16584                     q++;
16585                     opt->value = n;
16586                 }
16587                 cps->comboList[cps->comboCnt++] = q;
16588             }
16589             cps->comboList[cps->comboCnt++] = NULL;
16590             opt->max = n + 1;
16591             opt->type = ComboBox;
16592         } else if(p = strstr(opt->name, " -button")) {
16593             opt->type = Button;
16594         } else if(p = strstr(opt->name, " -save")) {
16595             opt->type = SaveButton;
16596         } else return FALSE;
16597         *p = 0; // terminate option name
16598         // now look if the command-line options define a setting for this engine option.
16599         if(cps->optionSettings && cps->optionSettings[0])
16600             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16601         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16602           snprintf(buf, MSG_SIZ, "option %s", p);
16603                 if(p = strstr(buf, ",")) *p = 0;
16604                 if(q = strchr(buf, '=')) switch(opt->type) {
16605                     case ComboBox:
16606                         for(n=0; n<opt->max; n++)
16607                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16608                         break;
16609                     case TextBox:
16610                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16611                         break;
16612                     case Spin:
16613                     case CheckBox:
16614                         opt->value = atoi(q+1);
16615                     default:
16616                         break;
16617                 }
16618                 strcat(buf, "\n");
16619                 SendToProgram(buf, cps);
16620         }
16621         return TRUE;
16622 }
16623
16624 void
16625 FeatureDone (ChessProgramState *cps, int val)
16626 {
16627   DelayedEventCallback cb = GetDelayedEvent();
16628   if ((cb == InitBackEnd3 && cps == &first) ||
16629       (cb == SettingsMenuIfReady && cps == &second) ||
16630       (cb == LoadEngine) ||
16631       (cb == TwoMachinesEventIfReady)) {
16632     CancelDelayedEvent();
16633     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16634   }
16635   cps->initDone = val;
16636   if(val) cps->reload = FALSE;
16637 }
16638
16639 /* Parse feature command from engine */
16640 void
16641 ParseFeatures (char *args, ChessProgramState *cps)
16642 {
16643   char *p = args;
16644   char *q = NULL;
16645   int val;
16646   char buf[MSG_SIZ];
16647
16648   for (;;) {
16649     while (*p == ' ') p++;
16650     if (*p == NULLCHAR) return;
16651
16652     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16653     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16654     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16655     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16656     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16657     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16658     if (BoolFeature(&p, "reuse", &val, cps)) {
16659       /* Engine can disable reuse, but can't enable it if user said no */
16660       if (!val) cps->reuse = FALSE;
16661       continue;
16662     }
16663     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16664     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16665       if (gameMode == TwoMachinesPlay) {
16666         DisplayTwoMachinesTitle();
16667       } else {
16668         DisplayTitle("");
16669       }
16670       continue;
16671     }
16672     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16673     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16674     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16675     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16676     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16677     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16678     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16679     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16680     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16681     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16682     if (IntFeature(&p, "done", &val, cps)) {
16683       FeatureDone(cps, val);
16684       continue;
16685     }
16686     /* Added by Tord: */
16687     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16688     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16689     /* End of additions by Tord */
16690
16691     /* [HGM] added features: */
16692     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16693     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16694     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16695     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16696     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16697     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16698     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16699     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16700         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16701         FREE(cps->option[cps->nrOptions].name);
16702         cps->option[cps->nrOptions].name = q; q = NULL;
16703         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16704           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16705             SendToProgram(buf, cps);
16706             continue;
16707         }
16708         if(cps->nrOptions >= MAX_OPTIONS) {
16709             cps->nrOptions--;
16710             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16711             DisplayError(buf, 0);
16712         }
16713         continue;
16714     }
16715     /* End of additions by HGM */
16716
16717     /* unknown feature: complain and skip */
16718     q = p;
16719     while (*q && *q != '=') q++;
16720     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16721     SendToProgram(buf, cps);
16722     p = q;
16723     if (*p == '=') {
16724       p++;
16725       if (*p == '\"') {
16726         p++;
16727         while (*p && *p != '\"') p++;
16728         if (*p == '\"') p++;
16729       } else {
16730         while (*p && *p != ' ') p++;
16731       }
16732     }
16733   }
16734
16735 }
16736
16737 void
16738 PeriodicUpdatesEvent (int newState)
16739 {
16740     if (newState == appData.periodicUpdates)
16741       return;
16742
16743     appData.periodicUpdates=newState;
16744
16745     /* Display type changes, so update it now */
16746 //    DisplayAnalysis();
16747
16748     /* Get the ball rolling again... */
16749     if (newState) {
16750         AnalysisPeriodicEvent(1);
16751         StartAnalysisClock();
16752     }
16753 }
16754
16755 void
16756 PonderNextMoveEvent (int newState)
16757 {
16758     if (newState == appData.ponderNextMove) return;
16759     if (gameMode == EditPosition) EditPositionDone(TRUE);
16760     if (newState) {
16761         SendToProgram("hard\n", &first);
16762         if (gameMode == TwoMachinesPlay) {
16763             SendToProgram("hard\n", &second);
16764         }
16765     } else {
16766         SendToProgram("easy\n", &first);
16767         thinkOutput[0] = NULLCHAR;
16768         if (gameMode == TwoMachinesPlay) {
16769             SendToProgram("easy\n", &second);
16770         }
16771     }
16772     appData.ponderNextMove = newState;
16773 }
16774
16775 void
16776 NewSettingEvent (int option, int *feature, char *command, int value)
16777 {
16778     char buf[MSG_SIZ];
16779
16780     if (gameMode == EditPosition) EditPositionDone(TRUE);
16781     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16782     if(feature == NULL || *feature) SendToProgram(buf, &first);
16783     if (gameMode == TwoMachinesPlay) {
16784         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16785     }
16786 }
16787
16788 void
16789 ShowThinkingEvent ()
16790 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16791 {
16792     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16793     int newState = appData.showThinking
16794         // [HGM] thinking: other features now need thinking output as well
16795         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16796
16797     if (oldState == newState) return;
16798     oldState = newState;
16799     if (gameMode == EditPosition) EditPositionDone(TRUE);
16800     if (oldState) {
16801         SendToProgram("post\n", &first);
16802         if (gameMode == TwoMachinesPlay) {
16803             SendToProgram("post\n", &second);
16804         }
16805     } else {
16806         SendToProgram("nopost\n", &first);
16807         thinkOutput[0] = NULLCHAR;
16808         if (gameMode == TwoMachinesPlay) {
16809             SendToProgram("nopost\n", &second);
16810         }
16811     }
16812 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16813 }
16814
16815 void
16816 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16817 {
16818   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16819   if (pr == NoProc) return;
16820   AskQuestion(title, question, replyPrefix, pr);
16821 }
16822
16823 void
16824 TypeInEvent (char firstChar)
16825 {
16826     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16827         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16828         gameMode == AnalyzeMode || gameMode == EditGame ||
16829         gameMode == EditPosition || gameMode == IcsExamining ||
16830         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16831         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16832                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16833                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16834         gameMode == Training) PopUpMoveDialog(firstChar);
16835 }
16836
16837 void
16838 TypeInDoneEvent (char *move)
16839 {
16840         Board board;
16841         int n, fromX, fromY, toX, toY;
16842         char promoChar;
16843         ChessMove moveType;
16844
16845         // [HGM] FENedit
16846         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16847                 EditPositionPasteFEN(move);
16848                 return;
16849         }
16850         // [HGM] movenum: allow move number to be typed in any mode
16851         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16852           ToNrEvent(2*n-1);
16853           return;
16854         }
16855         // undocumented kludge: allow command-line option to be typed in!
16856         // (potentially fatal, and does not implement the effect of the option.)
16857         // should only be used for options that are values on which future decisions will be made,
16858         // and definitely not on options that would be used during initialization.
16859         if(strstr(move, "!!! -") == move) {
16860             ParseArgsFromString(move+4);
16861             return;
16862         }
16863
16864       if (gameMode != EditGame && currentMove != forwardMostMove &&
16865         gameMode != Training) {
16866         DisplayMoveError(_("Displayed move is not current"));
16867       } else {
16868         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16869           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16870         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16871         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16872           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16873           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16874         } else {
16875           DisplayMoveError(_("Could not parse move"));
16876         }
16877       }
16878 }
16879
16880 void
16881 DisplayMove (int moveNumber)
16882 {
16883     char message[MSG_SIZ];
16884     char res[MSG_SIZ];
16885     char cpThinkOutput[MSG_SIZ];
16886
16887     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16888
16889     if (moveNumber == forwardMostMove - 1 ||
16890         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16891
16892         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16893
16894         if (strchr(cpThinkOutput, '\n')) {
16895             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16896         }
16897     } else {
16898         *cpThinkOutput = NULLCHAR;
16899     }
16900
16901     /* [AS] Hide thinking from human user */
16902     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16903         *cpThinkOutput = NULLCHAR;
16904         if( thinkOutput[0] != NULLCHAR ) {
16905             int i;
16906
16907             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16908                 cpThinkOutput[i] = '.';
16909             }
16910             cpThinkOutput[i] = NULLCHAR;
16911             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16912         }
16913     }
16914
16915     if (moveNumber == forwardMostMove - 1 &&
16916         gameInfo.resultDetails != NULL) {
16917         if (gameInfo.resultDetails[0] == NULLCHAR) {
16918           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16919         } else {
16920           snprintf(res, MSG_SIZ, " {%s} %s",
16921                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16922         }
16923     } else {
16924         res[0] = NULLCHAR;
16925     }
16926
16927     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16928         DisplayMessage(res, cpThinkOutput);
16929     } else {
16930       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16931                 WhiteOnMove(moveNumber) ? " " : ".. ",
16932                 parseList[moveNumber], res);
16933         DisplayMessage(message, cpThinkOutput);
16934     }
16935 }
16936
16937 void
16938 DisplayComment (int moveNumber, char *text)
16939 {
16940     char title[MSG_SIZ];
16941
16942     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16943       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16944     } else {
16945       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16946               WhiteOnMove(moveNumber) ? " " : ".. ",
16947               parseList[moveNumber]);
16948     }
16949     if (text != NULL && (appData.autoDisplayComment || commentUp))
16950         CommentPopUp(title, text);
16951 }
16952
16953 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16954  * might be busy thinking or pondering.  It can be omitted if your
16955  * gnuchess is configured to stop thinking immediately on any user
16956  * input.  However, that gnuchess feature depends on the FIONREAD
16957  * ioctl, which does not work properly on some flavors of Unix.
16958  */
16959 void
16960 Attention (ChessProgramState *cps)
16961 {
16962 #if ATTENTION
16963     if (!cps->useSigint) return;
16964     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16965     switch (gameMode) {
16966       case MachinePlaysWhite:
16967       case MachinePlaysBlack:
16968       case TwoMachinesPlay:
16969       case IcsPlayingWhite:
16970       case IcsPlayingBlack:
16971       case AnalyzeMode:
16972       case AnalyzeFile:
16973         /* Skip if we know it isn't thinking */
16974         if (!cps->maybeThinking) return;
16975         if (appData.debugMode)
16976           fprintf(debugFP, "Interrupting %s\n", cps->which);
16977         InterruptChildProcess(cps->pr);
16978         cps->maybeThinking = FALSE;
16979         break;
16980       default:
16981         break;
16982     }
16983 #endif /*ATTENTION*/
16984 }
16985
16986 int
16987 CheckFlags ()
16988 {
16989     if (whiteTimeRemaining <= 0) {
16990         if (!whiteFlag) {
16991             whiteFlag = TRUE;
16992             if (appData.icsActive) {
16993                 if (appData.autoCallFlag &&
16994                     gameMode == IcsPlayingBlack && !blackFlag) {
16995                   SendToICS(ics_prefix);
16996                   SendToICS("flag\n");
16997                 }
16998             } else {
16999                 if (blackFlag) {
17000                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17001                 } else {
17002                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17003                     if (appData.autoCallFlag) {
17004                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17005                         return TRUE;
17006                     }
17007                 }
17008             }
17009         }
17010     }
17011     if (blackTimeRemaining <= 0) {
17012         if (!blackFlag) {
17013             blackFlag = TRUE;
17014             if (appData.icsActive) {
17015                 if (appData.autoCallFlag &&
17016                     gameMode == IcsPlayingWhite && !whiteFlag) {
17017                   SendToICS(ics_prefix);
17018                   SendToICS("flag\n");
17019                 }
17020             } else {
17021                 if (whiteFlag) {
17022                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17023                 } else {
17024                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17025                     if (appData.autoCallFlag) {
17026                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17027                         return TRUE;
17028                     }
17029                 }
17030             }
17031         }
17032     }
17033     return FALSE;
17034 }
17035
17036 void
17037 CheckTimeControl ()
17038 {
17039     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17040         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17041
17042     /*
17043      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17044      */
17045     if ( !WhiteOnMove(forwardMostMove) ) {
17046         /* White made time control */
17047         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17048         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17049         /* [HGM] time odds: correct new time quota for time odds! */
17050                                             / WhitePlayer()->timeOdds;
17051         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17052     } else {
17053         lastBlack -= blackTimeRemaining;
17054         /* Black made time control */
17055         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17056                                             / WhitePlayer()->other->timeOdds;
17057         lastWhite = whiteTimeRemaining;
17058     }
17059 }
17060
17061 void
17062 DisplayBothClocks ()
17063 {
17064     int wom = gameMode == EditPosition ?
17065       !blackPlaysFirst : WhiteOnMove(currentMove);
17066     DisplayWhiteClock(whiteTimeRemaining, wom);
17067     DisplayBlackClock(blackTimeRemaining, !wom);
17068 }
17069
17070
17071 /* Timekeeping seems to be a portability nightmare.  I think everyone
17072    has ftime(), but I'm really not sure, so I'm including some ifdefs
17073    to use other calls if you don't.  Clocks will be less accurate if
17074    you have neither ftime nor gettimeofday.
17075 */
17076
17077 /* VS 2008 requires the #include outside of the function */
17078 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17079 #include <sys/timeb.h>
17080 #endif
17081
17082 /* Get the current time as a TimeMark */
17083 void
17084 GetTimeMark (TimeMark *tm)
17085 {
17086 #if HAVE_GETTIMEOFDAY
17087
17088     struct timeval timeVal;
17089     struct timezone timeZone;
17090
17091     gettimeofday(&timeVal, &timeZone);
17092     tm->sec = (long) timeVal.tv_sec;
17093     tm->ms = (int) (timeVal.tv_usec / 1000L);
17094
17095 #else /*!HAVE_GETTIMEOFDAY*/
17096 #if HAVE_FTIME
17097
17098 // include <sys/timeb.h> / moved to just above start of function
17099     struct timeb timeB;
17100
17101     ftime(&timeB);
17102     tm->sec = (long) timeB.time;
17103     tm->ms = (int) timeB.millitm;
17104
17105 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17106     tm->sec = (long) time(NULL);
17107     tm->ms = 0;
17108 #endif
17109 #endif
17110 }
17111
17112 /* Return the difference in milliseconds between two
17113    time marks.  We assume the difference will fit in a long!
17114 */
17115 long
17116 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17117 {
17118     return 1000L*(tm2->sec - tm1->sec) +
17119            (long) (tm2->ms - tm1->ms);
17120 }
17121
17122
17123 /*
17124  * Code to manage the game clocks.
17125  *
17126  * In tournament play, black starts the clock and then white makes a move.
17127  * We give the human user a slight advantage if he is playing white---the
17128  * clocks don't run until he makes his first move, so it takes zero time.
17129  * Also, we don't account for network lag, so we could get out of sync
17130  * with GNU Chess's clock -- but then, referees are always right.
17131  */
17132
17133 static TimeMark tickStartTM;
17134 static long intendedTickLength;
17135
17136 long
17137 NextTickLength (long timeRemaining)
17138 {
17139     long nominalTickLength, nextTickLength;
17140
17141     if (timeRemaining > 0L && timeRemaining <= 10000L)
17142       nominalTickLength = 100L;
17143     else
17144       nominalTickLength = 1000L;
17145     nextTickLength = timeRemaining % nominalTickLength;
17146     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17147
17148     return nextTickLength;
17149 }
17150
17151 /* Adjust clock one minute up or down */
17152 void
17153 AdjustClock (Boolean which, int dir)
17154 {
17155     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17156     if(which) blackTimeRemaining += 60000*dir;
17157     else      whiteTimeRemaining += 60000*dir;
17158     DisplayBothClocks();
17159     adjustedClock = TRUE;
17160 }
17161
17162 /* Stop clocks and reset to a fresh time control */
17163 void
17164 ResetClocks ()
17165 {
17166     (void) StopClockTimer();
17167     if (appData.icsActive) {
17168         whiteTimeRemaining = blackTimeRemaining = 0;
17169     } else if (searchTime) {
17170         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17171         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17172     } else { /* [HGM] correct new time quote for time odds */
17173         whiteTC = blackTC = fullTimeControlString;
17174         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17175         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17176     }
17177     if (whiteFlag || blackFlag) {
17178         DisplayTitle("");
17179         whiteFlag = blackFlag = FALSE;
17180     }
17181     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17182     DisplayBothClocks();
17183     adjustedClock = FALSE;
17184 }
17185
17186 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17187
17188 /* Decrement running clock by amount of time that has passed */
17189 void
17190 DecrementClocks ()
17191 {
17192     long timeRemaining;
17193     long lastTickLength, fudge;
17194     TimeMark now;
17195
17196     if (!appData.clockMode) return;
17197     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17198
17199     GetTimeMark(&now);
17200
17201     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17202
17203     /* Fudge if we woke up a little too soon */
17204     fudge = intendedTickLength - lastTickLength;
17205     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17206
17207     if (WhiteOnMove(forwardMostMove)) {
17208         if(whiteNPS >= 0) lastTickLength = 0;
17209         timeRemaining = whiteTimeRemaining -= lastTickLength;
17210         if(timeRemaining < 0 && !appData.icsActive) {
17211             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17212             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17213                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17214                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17215             }
17216         }
17217         DisplayWhiteClock(whiteTimeRemaining - fudge,
17218                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17219     } else {
17220         if(blackNPS >= 0) lastTickLength = 0;
17221         timeRemaining = blackTimeRemaining -= lastTickLength;
17222         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17223             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17224             if(suddenDeath) {
17225                 blackStartMove = forwardMostMove;
17226                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17227             }
17228         }
17229         DisplayBlackClock(blackTimeRemaining - fudge,
17230                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17231     }
17232     if (CheckFlags()) return;
17233
17234     if(twoBoards) { // count down secondary board's clocks as well
17235         activePartnerTime -= lastTickLength;
17236         partnerUp = 1;
17237         if(activePartner == 'W')
17238             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17239         else
17240             DisplayBlackClock(activePartnerTime, TRUE);
17241         partnerUp = 0;
17242     }
17243
17244     tickStartTM = now;
17245     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17246     StartClockTimer(intendedTickLength);
17247
17248     /* if the time remaining has fallen below the alarm threshold, sound the
17249      * alarm. if the alarm has sounded and (due to a takeback or time control
17250      * with increment) the time remaining has increased to a level above the
17251      * threshold, reset the alarm so it can sound again.
17252      */
17253
17254     if (appData.icsActive && appData.icsAlarm) {
17255
17256         /* make sure we are dealing with the user's clock */
17257         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17258                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17259            )) return;
17260
17261         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17262             alarmSounded = FALSE;
17263         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17264             PlayAlarmSound();
17265             alarmSounded = TRUE;
17266         }
17267     }
17268 }
17269
17270
17271 /* A player has just moved, so stop the previously running
17272    clock and (if in clock mode) start the other one.
17273    We redisplay both clocks in case we're in ICS mode, because
17274    ICS gives us an update to both clocks after every move.
17275    Note that this routine is called *after* forwardMostMove
17276    is updated, so the last fractional tick must be subtracted
17277    from the color that is *not* on move now.
17278 */
17279 void
17280 SwitchClocks (int newMoveNr)
17281 {
17282     long lastTickLength;
17283     TimeMark now;
17284     int flagged = FALSE;
17285
17286     GetTimeMark(&now);
17287
17288     if (StopClockTimer() && appData.clockMode) {
17289         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17290         if (!WhiteOnMove(forwardMostMove)) {
17291             if(blackNPS >= 0) lastTickLength = 0;
17292             blackTimeRemaining -= lastTickLength;
17293            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17294 //         if(pvInfoList[forwardMostMove].time == -1)
17295                  pvInfoList[forwardMostMove].time =               // use GUI time
17296                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17297         } else {
17298            if(whiteNPS >= 0) lastTickLength = 0;
17299            whiteTimeRemaining -= lastTickLength;
17300            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17301 //         if(pvInfoList[forwardMostMove].time == -1)
17302                  pvInfoList[forwardMostMove].time =
17303                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17304         }
17305         flagged = CheckFlags();
17306     }
17307     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17308     CheckTimeControl();
17309
17310     if (flagged || !appData.clockMode) return;
17311
17312     switch (gameMode) {
17313       case MachinePlaysBlack:
17314       case MachinePlaysWhite:
17315       case BeginningOfGame:
17316         if (pausing) return;
17317         break;
17318
17319       case EditGame:
17320       case PlayFromGameFile:
17321       case IcsExamining:
17322         return;
17323
17324       default:
17325         break;
17326     }
17327
17328     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17329         if(WhiteOnMove(forwardMostMove))
17330              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17331         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17332     }
17333
17334     tickStartTM = now;
17335     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17336       whiteTimeRemaining : blackTimeRemaining);
17337     StartClockTimer(intendedTickLength);
17338 }
17339
17340
17341 /* Stop both clocks */
17342 void
17343 StopClocks ()
17344 {
17345     long lastTickLength;
17346     TimeMark now;
17347
17348     if (!StopClockTimer()) return;
17349     if (!appData.clockMode) return;
17350
17351     GetTimeMark(&now);
17352
17353     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17354     if (WhiteOnMove(forwardMostMove)) {
17355         if(whiteNPS >= 0) lastTickLength = 0;
17356         whiteTimeRemaining -= lastTickLength;
17357         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17358     } else {
17359         if(blackNPS >= 0) lastTickLength = 0;
17360         blackTimeRemaining -= lastTickLength;
17361         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17362     }
17363     CheckFlags();
17364 }
17365
17366 /* Start clock of player on move.  Time may have been reset, so
17367    if clock is already running, stop and restart it. */
17368 void
17369 StartClocks ()
17370 {
17371     (void) StopClockTimer(); /* in case it was running already */
17372     DisplayBothClocks();
17373     if (CheckFlags()) return;
17374
17375     if (!appData.clockMode) return;
17376     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17377
17378     GetTimeMark(&tickStartTM);
17379     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17380       whiteTimeRemaining : blackTimeRemaining);
17381
17382    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17383     whiteNPS = blackNPS = -1;
17384     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17385        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17386         whiteNPS = first.nps;
17387     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17388        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17389         blackNPS = first.nps;
17390     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17391         whiteNPS = second.nps;
17392     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17393         blackNPS = second.nps;
17394     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17395
17396     StartClockTimer(intendedTickLength);
17397 }
17398
17399 char *
17400 TimeString (long ms)
17401 {
17402     long second, minute, hour, day;
17403     char *sign = "";
17404     static char buf[32];
17405
17406     if (ms > 0 && ms <= 9900) {
17407       /* convert milliseconds to tenths, rounding up */
17408       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17409
17410       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17411       return buf;
17412     }
17413
17414     /* convert milliseconds to seconds, rounding up */
17415     /* use floating point to avoid strangeness of integer division
17416        with negative dividends on many machines */
17417     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17418
17419     if (second < 0) {
17420         sign = "-";
17421         second = -second;
17422     }
17423
17424     day = second / (60 * 60 * 24);
17425     second = second % (60 * 60 * 24);
17426     hour = second / (60 * 60);
17427     second = second % (60 * 60);
17428     minute = second / 60;
17429     second = second % 60;
17430
17431     if (day > 0)
17432       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17433               sign, day, hour, minute, second);
17434     else if (hour > 0)
17435       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17436     else
17437       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17438
17439     return buf;
17440 }
17441
17442
17443 /*
17444  * This is necessary because some C libraries aren't ANSI C compliant yet.
17445  */
17446 char *
17447 StrStr (char *string, char *match)
17448 {
17449     int i, length;
17450
17451     length = strlen(match);
17452
17453     for (i = strlen(string) - length; i >= 0; i--, string++)
17454       if (!strncmp(match, string, length))
17455         return string;
17456
17457     return NULL;
17458 }
17459
17460 char *
17461 StrCaseStr (char *string, char *match)
17462 {
17463     int i, j, length;
17464
17465     length = strlen(match);
17466
17467     for (i = strlen(string) - length; i >= 0; i--, string++) {
17468         for (j = 0; j < length; j++) {
17469             if (ToLower(match[j]) != ToLower(string[j]))
17470               break;
17471         }
17472         if (j == length) return string;
17473     }
17474
17475     return NULL;
17476 }
17477
17478 #ifndef _amigados
17479 int
17480 StrCaseCmp (char *s1, char *s2)
17481 {
17482     char c1, c2;
17483
17484     for (;;) {
17485         c1 = ToLower(*s1++);
17486         c2 = ToLower(*s2++);
17487         if (c1 > c2) return 1;
17488         if (c1 < c2) return -1;
17489         if (c1 == NULLCHAR) return 0;
17490     }
17491 }
17492
17493
17494 int
17495 ToLower (int c)
17496 {
17497     return isupper(c) ? tolower(c) : c;
17498 }
17499
17500
17501 int
17502 ToUpper (int c)
17503 {
17504     return islower(c) ? toupper(c) : c;
17505 }
17506 #endif /* !_amigados    */
17507
17508 char *
17509 StrSave (char *s)
17510 {
17511   char *ret;
17512
17513   if ((ret = (char *) malloc(strlen(s) + 1)))
17514     {
17515       safeStrCpy(ret, s, strlen(s)+1);
17516     }
17517   return ret;
17518 }
17519
17520 char *
17521 StrSavePtr (char *s, char **savePtr)
17522 {
17523     if (*savePtr) {
17524         free(*savePtr);
17525     }
17526     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17527       safeStrCpy(*savePtr, s, strlen(s)+1);
17528     }
17529     return(*savePtr);
17530 }
17531
17532 char *
17533 PGNDate ()
17534 {
17535     time_t clock;
17536     struct tm *tm;
17537     char buf[MSG_SIZ];
17538
17539     clock = time((time_t *)NULL);
17540     tm = localtime(&clock);
17541     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17542             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17543     return StrSave(buf);
17544 }
17545
17546
17547 char *
17548 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17549 {
17550     int i, j, fromX, fromY, toX, toY;
17551     int whiteToPlay;
17552     char buf[MSG_SIZ];
17553     char *p, *q;
17554     int emptycount;
17555     ChessSquare piece;
17556
17557     whiteToPlay = (gameMode == EditPosition) ?
17558       !blackPlaysFirst : (move % 2 == 0);
17559     p = buf;
17560
17561     /* Piece placement data */
17562     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17563         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17564         emptycount = 0;
17565         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17566             if (boards[move][i][j] == EmptySquare) {
17567                 emptycount++;
17568             } else { ChessSquare piece = boards[move][i][j];
17569                 if (emptycount > 0) {
17570                     if(emptycount<10) /* [HGM] can be >= 10 */
17571                         *p++ = '0' + emptycount;
17572                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17573                     emptycount = 0;
17574                 }
17575                 if(PieceToChar(piece) == '+') {
17576                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17577                     *p++ = '+';
17578                     piece = (ChessSquare)(DEMOTED piece);
17579                 }
17580                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17581                 if(p[-1] == '~') {
17582                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17583                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17584                     *p++ = '~';
17585                 }
17586             }
17587         }
17588         if (emptycount > 0) {
17589             if(emptycount<10) /* [HGM] can be >= 10 */
17590                 *p++ = '0' + emptycount;
17591             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17592             emptycount = 0;
17593         }
17594         *p++ = '/';
17595     }
17596     *(p - 1) = ' ';
17597
17598     /* [HGM] print Crazyhouse or Shogi holdings */
17599     if( gameInfo.holdingsWidth ) {
17600         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17601         q = p;
17602         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17603             piece = boards[move][i][BOARD_WIDTH-1];
17604             if( piece != EmptySquare )
17605               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17606                   *p++ = PieceToChar(piece);
17607         }
17608         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17609             piece = boards[move][BOARD_HEIGHT-i-1][0];
17610             if( piece != EmptySquare )
17611               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17612                   *p++ = PieceToChar(piece);
17613         }
17614
17615         if( q == p ) *p++ = '-';
17616         *p++ = ']';
17617         *p++ = ' ';
17618     }
17619
17620     /* Active color */
17621     *p++ = whiteToPlay ? 'w' : 'b';
17622     *p++ = ' ';
17623
17624   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17625     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17626   } else {
17627   if(nrCastlingRights) {
17628      q = p;
17629      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17630        /* [HGM] write directly from rights */
17631            if(boards[move][CASTLING][2] != NoRights &&
17632               boards[move][CASTLING][0] != NoRights   )
17633                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17634            if(boards[move][CASTLING][2] != NoRights &&
17635               boards[move][CASTLING][1] != NoRights   )
17636                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17637            if(boards[move][CASTLING][5] != NoRights &&
17638               boards[move][CASTLING][3] != NoRights   )
17639                 *p++ = boards[move][CASTLING][3] + AAA;
17640            if(boards[move][CASTLING][5] != NoRights &&
17641               boards[move][CASTLING][4] != NoRights   )
17642                 *p++ = boards[move][CASTLING][4] + AAA;
17643      } else {
17644
17645         /* [HGM] write true castling rights */
17646         if( nrCastlingRights == 6 ) {
17647             int q, k=0;
17648             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17649                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17650             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17651                  boards[move][CASTLING][2] != NoRights  );
17652             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17653                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17654                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17655                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17656                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17657             }
17658             if(q) *p++ = 'Q';
17659             k = 0;
17660             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17661                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17662             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17663                  boards[move][CASTLING][5] != NoRights  );
17664             if(gameInfo.variant == VariantSChess) {
17665                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17666                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17667                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17668                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17669             }
17670             if(q) *p++ = 'q';
17671         }
17672      }
17673      if (q == p) *p++ = '-'; /* No castling rights */
17674      *p++ = ' ';
17675   }
17676
17677   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17678      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17679      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17680     /* En passant target square */
17681     if (move > backwardMostMove) {
17682         fromX = moveList[move - 1][0] - AAA;
17683         fromY = moveList[move - 1][1] - ONE;
17684         toX = moveList[move - 1][2] - AAA;
17685         toY = moveList[move - 1][3] - ONE;
17686         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17687             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17688             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17689             fromX == toX) {
17690             /* 2-square pawn move just happened */
17691             *p++ = toX + AAA;
17692             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17693         } else {
17694             *p++ = '-';
17695         }
17696     } else if(move == backwardMostMove) {
17697         // [HGM] perhaps we should always do it like this, and forget the above?
17698         if((signed char)boards[move][EP_STATUS] >= 0) {
17699             *p++ = boards[move][EP_STATUS] + AAA;
17700             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17701         } else {
17702             *p++ = '-';
17703         }
17704     } else {
17705         *p++ = '-';
17706     }
17707     *p++ = ' ';
17708   }
17709   }
17710
17711     if(moveCounts)
17712     {   int i = 0, j=move;
17713
17714         /* [HGM] find reversible plies */
17715         if (appData.debugMode) { int k;
17716             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17717             for(k=backwardMostMove; k<=forwardMostMove; k++)
17718                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17719
17720         }
17721
17722         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17723         if( j == backwardMostMove ) i += initialRulePlies;
17724         sprintf(p, "%d ", i);
17725         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17726
17727         /* Fullmove number */
17728         sprintf(p, "%d", (move / 2) + 1);
17729     } else *--p = NULLCHAR;
17730
17731     return StrSave(buf);
17732 }
17733
17734 Boolean
17735 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17736 {
17737     int i, j, k, w=0;
17738     char *p, c;
17739     int emptycount, virgin[BOARD_FILES];
17740     ChessSquare piece;
17741
17742     p = fen;
17743
17744     /* Piece placement data */
17745     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17746         j = 0;
17747         for (;;) {
17748             if (*p == '/' || *p == ' ' || *p == '[' ) {
17749                 if(j > w) w = j;
17750                 emptycount = gameInfo.boardWidth - j;
17751                 while (emptycount--)
17752                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17753                 if (*p == '/') p++;
17754                 else if(autoSize) { // we stumbled unexpectedly into end of board
17755                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17756                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17757                     }
17758                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17759                 }
17760                 break;
17761 #if(BOARD_FILES >= 10)
17762             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17763                 p++; emptycount=10;
17764                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17765                 while (emptycount--)
17766                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17767 #endif
17768             } else if (*p == '*') {
17769                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17770             } else if (isdigit(*p)) {
17771                 emptycount = *p++ - '0';
17772                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17773                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17774                 while (emptycount--)
17775                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17776             } else if (*p == '+' || isalpha(*p)) {
17777                 if (j >= gameInfo.boardWidth) return FALSE;
17778                 if(*p=='+') {
17779                     piece = CharToPiece(*++p);
17780                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17781                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17782                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17783                 } else piece = CharToPiece(*p++);
17784
17785                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17786                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17787                     piece = (ChessSquare) (PROMOTED piece);
17788                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17789                     p++;
17790                 }
17791                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17792             } else {
17793                 return FALSE;
17794             }
17795         }
17796     }
17797     while (*p == '/' || *p == ' ') p++;
17798
17799     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17800
17801     /* [HGM] by default clear Crazyhouse holdings, if present */
17802     if(gameInfo.holdingsWidth) {
17803        for(i=0; i<BOARD_HEIGHT; i++) {
17804            board[i][0]             = EmptySquare; /* black holdings */
17805            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17806            board[i][1]             = (ChessSquare) 0; /* black counts */
17807            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17808        }
17809     }
17810
17811     /* [HGM] look for Crazyhouse holdings here */
17812     while(*p==' ') p++;
17813     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17814         if(*p == '[') p++;
17815         if(*p == '-' ) p++; /* empty holdings */ else {
17816             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17817             /* if we would allow FEN reading to set board size, we would   */
17818             /* have to add holdings and shift the board read so far here   */
17819             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17820                 p++;
17821                 if((int) piece >= (int) BlackPawn ) {
17822                     i = (int)piece - (int)BlackPawn;
17823                     i = PieceToNumber((ChessSquare)i);
17824                     if( i >= gameInfo.holdingsSize ) return FALSE;
17825                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17826                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17827                 } else {
17828                     i = (int)piece - (int)WhitePawn;
17829                     i = PieceToNumber((ChessSquare)i);
17830                     if( i >= gameInfo.holdingsSize ) return FALSE;
17831                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17832                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17833                 }
17834             }
17835         }
17836         if(*p == ']') p++;
17837     }
17838
17839     while(*p == ' ') p++;
17840
17841     /* Active color */
17842     c = *p++;
17843     if(appData.colorNickNames) {
17844       if( c == appData.colorNickNames[0] ) c = 'w'; else
17845       if( c == appData.colorNickNames[1] ) c = 'b';
17846     }
17847     switch (c) {
17848       case 'w':
17849         *blackPlaysFirst = FALSE;
17850         break;
17851       case 'b':
17852         *blackPlaysFirst = TRUE;
17853         break;
17854       default:
17855         return FALSE;
17856     }
17857
17858     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17859     /* return the extra info in global variiables             */
17860
17861     /* set defaults in case FEN is incomplete */
17862     board[EP_STATUS] = EP_UNKNOWN;
17863     for(i=0; i<nrCastlingRights; i++ ) {
17864         board[CASTLING][i] =
17865             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17866     }   /* assume possible unless obviously impossible */
17867     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17868     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17869     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17870                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17871     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17872     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17873     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17874                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17875     FENrulePlies = 0;
17876
17877     while(*p==' ') p++;
17878     if(nrCastlingRights) {
17879       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17880       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17881           /* castling indicator present, so default becomes no castlings */
17882           for(i=0; i<nrCastlingRights; i++ ) {
17883                  board[CASTLING][i] = NoRights;
17884           }
17885       }
17886       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17887              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17888              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17889              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17890         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17891
17892         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17893             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17894             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17895         }
17896         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17897             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17898         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17899                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17900         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17901                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17902         switch(c) {
17903           case'K':
17904               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17905               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17906               board[CASTLING][2] = whiteKingFile;
17907               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17908               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17909               break;
17910           case'Q':
17911               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17912               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17913               board[CASTLING][2] = whiteKingFile;
17914               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17915               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17916               break;
17917           case'k':
17918               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17919               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17920               board[CASTLING][5] = blackKingFile;
17921               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17922               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17923               break;
17924           case'q':
17925               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17926               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17927               board[CASTLING][5] = blackKingFile;
17928               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17929               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17930           case '-':
17931               break;
17932           default: /* FRC castlings */
17933               if(c >= 'a') { /* black rights */
17934                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17935                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17936                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17937                   if(i == BOARD_RGHT) break;
17938                   board[CASTLING][5] = i;
17939                   c -= AAA;
17940                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17941                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17942                   if(c > i)
17943                       board[CASTLING][3] = c;
17944                   else
17945                       board[CASTLING][4] = c;
17946               } else { /* white rights */
17947                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17948                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17949                     if(board[0][i] == WhiteKing) break;
17950                   if(i == BOARD_RGHT) break;
17951                   board[CASTLING][2] = i;
17952                   c -= AAA - 'a' + 'A';
17953                   if(board[0][c] >= WhiteKing) break;
17954                   if(c > i)
17955                       board[CASTLING][0] = c;
17956                   else
17957                       board[CASTLING][1] = c;
17958               }
17959         }
17960       }
17961       for(i=0; i<nrCastlingRights; i++)
17962         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17963       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17964     if (appData.debugMode) {
17965         fprintf(debugFP, "FEN castling rights:");
17966         for(i=0; i<nrCastlingRights; i++)
17967         fprintf(debugFP, " %d", board[CASTLING][i]);
17968         fprintf(debugFP, "\n");
17969     }
17970
17971       while(*p==' ') p++;
17972     }
17973
17974     /* read e.p. field in games that know e.p. capture */
17975     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17976        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17977        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17978       if(*p=='-') {
17979         p++; board[EP_STATUS] = EP_NONE;
17980       } else {
17981          char c = *p++ - AAA;
17982
17983          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
17984          if(*p >= '0' && *p <='9') p++;
17985          board[EP_STATUS] = c;
17986       }
17987     }
17988
17989
17990     if(sscanf(p, "%d", &i) == 1) {
17991         FENrulePlies = i; /* 50-move ply counter */
17992         /* (The move number is still ignored)    */
17993     }
17994
17995     return TRUE;
17996 }
17997
17998 void
17999 EditPositionPasteFEN (char *fen)
18000 {
18001   if (fen != NULL) {
18002     Board initial_position;
18003
18004     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18005       DisplayError(_("Bad FEN position in clipboard"), 0);
18006       return ;
18007     } else {
18008       int savedBlackPlaysFirst = blackPlaysFirst;
18009       EditPositionEvent();
18010       blackPlaysFirst = savedBlackPlaysFirst;
18011       CopyBoard(boards[0], initial_position);
18012       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18013       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18014       DisplayBothClocks();
18015       DrawPosition(FALSE, boards[currentMove]);
18016     }
18017   }
18018 }
18019
18020 static char cseq[12] = "\\   ";
18021
18022 Boolean
18023 set_cont_sequence (char *new_seq)
18024 {
18025     int len;
18026     Boolean ret;
18027
18028     // handle bad attempts to set the sequence
18029         if (!new_seq)
18030                 return 0; // acceptable error - no debug
18031
18032     len = strlen(new_seq);
18033     ret = (len > 0) && (len < sizeof(cseq));
18034     if (ret)
18035       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18036     else if (appData.debugMode)
18037       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18038     return ret;
18039 }
18040
18041 /*
18042     reformat a source message so words don't cross the width boundary.  internal
18043     newlines are not removed.  returns the wrapped size (no null character unless
18044     included in source message).  If dest is NULL, only calculate the size required
18045     for the dest buffer.  lp argument indicats line position upon entry, and it's
18046     passed back upon exit.
18047 */
18048 int
18049 wrap (char *dest, char *src, int count, int width, int *lp)
18050 {
18051     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18052
18053     cseq_len = strlen(cseq);
18054     old_line = line = *lp;
18055     ansi = len = clen = 0;
18056
18057     for (i=0; i < count; i++)
18058     {
18059         if (src[i] == '\033')
18060             ansi = 1;
18061
18062         // if we hit the width, back up
18063         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18064         {
18065             // store i & len in case the word is too long
18066             old_i = i, old_len = len;
18067
18068             // find the end of the last word
18069             while (i && src[i] != ' ' && src[i] != '\n')
18070             {
18071                 i--;
18072                 len--;
18073             }
18074
18075             // word too long?  restore i & len before splitting it
18076             if ((old_i-i+clen) >= width)
18077             {
18078                 i = old_i;
18079                 len = old_len;
18080             }
18081
18082             // extra space?
18083             if (i && src[i-1] == ' ')
18084                 len--;
18085
18086             if (src[i] != ' ' && src[i] != '\n')
18087             {
18088                 i--;
18089                 if (len)
18090                     len--;
18091             }
18092
18093             // now append the newline and continuation sequence
18094             if (dest)
18095                 dest[len] = '\n';
18096             len++;
18097             if (dest)
18098                 strncpy(dest+len, cseq, cseq_len);
18099             len += cseq_len;
18100             line = cseq_len;
18101             clen = cseq_len;
18102             continue;
18103         }
18104
18105         if (dest)
18106             dest[len] = src[i];
18107         len++;
18108         if (!ansi)
18109             line++;
18110         if (src[i] == '\n')
18111             line = 0;
18112         if (src[i] == 'm')
18113             ansi = 0;
18114     }
18115     if (dest && appData.debugMode)
18116     {
18117         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18118             count, width, line, len, *lp);
18119         show_bytes(debugFP, src, count);
18120         fprintf(debugFP, "\ndest: ");
18121         show_bytes(debugFP, dest, len);
18122         fprintf(debugFP, "\n");
18123     }
18124     *lp = dest ? line : old_line;
18125
18126     return len;
18127 }
18128
18129 // [HGM] vari: routines for shelving variations
18130 Boolean modeRestore = FALSE;
18131
18132 void
18133 PushInner (int firstMove, int lastMove)
18134 {
18135         int i, j, nrMoves = lastMove - firstMove;
18136
18137         // push current tail of game on stack
18138         savedResult[storedGames] = gameInfo.result;
18139         savedDetails[storedGames] = gameInfo.resultDetails;
18140         gameInfo.resultDetails = NULL;
18141         savedFirst[storedGames] = firstMove;
18142         savedLast [storedGames] = lastMove;
18143         savedFramePtr[storedGames] = framePtr;
18144         framePtr -= nrMoves; // reserve space for the boards
18145         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18146             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18147             for(j=0; j<MOVE_LEN; j++)
18148                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18149             for(j=0; j<2*MOVE_LEN; j++)
18150                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18151             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18152             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18153             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18154             pvInfoList[firstMove+i-1].depth = 0;
18155             commentList[framePtr+i] = commentList[firstMove+i];
18156             commentList[firstMove+i] = NULL;
18157         }
18158
18159         storedGames++;
18160         forwardMostMove = firstMove; // truncate game so we can start variation
18161 }
18162
18163 void
18164 PushTail (int firstMove, int lastMove)
18165 {
18166         if(appData.icsActive) { // only in local mode
18167                 forwardMostMove = currentMove; // mimic old ICS behavior
18168                 return;
18169         }
18170         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18171
18172         PushInner(firstMove, lastMove);
18173         if(storedGames == 1) GreyRevert(FALSE);
18174         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18175 }
18176
18177 void
18178 PopInner (Boolean annotate)
18179 {
18180         int i, j, nrMoves;
18181         char buf[8000], moveBuf[20];
18182
18183         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18184         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18185         nrMoves = savedLast[storedGames] - currentMove;
18186         if(annotate) {
18187                 int cnt = 10;
18188                 if(!WhiteOnMove(currentMove))
18189                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18190                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18191                 for(i=currentMove; i<forwardMostMove; i++) {
18192                         if(WhiteOnMove(i))
18193                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18194                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18195                         strcat(buf, moveBuf);
18196                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18197                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18198                 }
18199                 strcat(buf, ")");
18200         }
18201         for(i=1; i<=nrMoves; i++) { // copy last variation back
18202             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18203             for(j=0; j<MOVE_LEN; j++)
18204                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18205             for(j=0; j<2*MOVE_LEN; j++)
18206                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18207             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18208             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18209             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18210             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18211             commentList[currentMove+i] = commentList[framePtr+i];
18212             commentList[framePtr+i] = NULL;
18213         }
18214         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18215         framePtr = savedFramePtr[storedGames];
18216         gameInfo.result = savedResult[storedGames];
18217         if(gameInfo.resultDetails != NULL) {
18218             free(gameInfo.resultDetails);
18219       }
18220         gameInfo.resultDetails = savedDetails[storedGames];
18221         forwardMostMove = currentMove + nrMoves;
18222 }
18223
18224 Boolean
18225 PopTail (Boolean annotate)
18226 {
18227         if(appData.icsActive) return FALSE; // only in local mode
18228         if(!storedGames) return FALSE; // sanity
18229         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18230
18231         PopInner(annotate);
18232         if(currentMove < forwardMostMove) ForwardEvent(); else
18233         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18234
18235         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18236         return TRUE;
18237 }
18238
18239 void
18240 CleanupTail ()
18241 {       // remove all shelved variations
18242         int i;
18243         for(i=0; i<storedGames; i++) {
18244             if(savedDetails[i])
18245                 free(savedDetails[i]);
18246             savedDetails[i] = NULL;
18247         }
18248         for(i=framePtr; i<MAX_MOVES; i++) {
18249                 if(commentList[i]) free(commentList[i]);
18250                 commentList[i] = NULL;
18251         }
18252         framePtr = MAX_MOVES-1;
18253         storedGames = 0;
18254 }
18255
18256 void
18257 LoadVariation (int index, char *text)
18258 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18259         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18260         int level = 0, move;
18261
18262         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18263         // first find outermost bracketing variation
18264         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18265             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18266                 if(*p == '{') wait = '}'; else
18267                 if(*p == '[') wait = ']'; else
18268                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18269                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18270             }
18271             if(*p == wait) wait = NULLCHAR; // closing ]} found
18272             p++;
18273         }
18274         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18275         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18276         end[1] = NULLCHAR; // clip off comment beyond variation
18277         ToNrEvent(currentMove-1);
18278         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18279         // kludge: use ParsePV() to append variation to game
18280         move = currentMove;
18281         ParsePV(start, TRUE, TRUE);
18282         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18283         ClearPremoveHighlights();
18284         CommentPopDown();
18285         ToNrEvent(currentMove+1);
18286 }
18287
18288 void
18289 LoadTheme ()
18290 {
18291     char *p, *q, buf[MSG_SIZ];
18292     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18293         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18294         ParseArgsFromString(buf);
18295         ActivateTheme(TRUE); // also redo colors
18296         return;
18297     }
18298     p = nickName;
18299     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18300     {
18301         int len;
18302         q = appData.themeNames;
18303         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18304       if(appData.useBitmaps) {
18305         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18306                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18307                 appData.liteBackTextureMode,
18308                 appData.darkBackTextureMode );
18309       } else {
18310         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18311                 Col2Text(2),   // lightSquareColor
18312                 Col2Text(3) ); // darkSquareColor
18313       }
18314       if(appData.useBorder) {
18315         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18316                 appData.border);
18317       } else {
18318         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18319       }
18320       if(appData.useFont) {
18321         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18322                 appData.renderPiecesWithFont,
18323                 appData.fontToPieceTable,
18324                 Col2Text(9),    // appData.fontBackColorWhite
18325                 Col2Text(10) ); // appData.fontForeColorBlack
18326       } else {
18327         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18328                 appData.pieceDirectory);
18329         if(!appData.pieceDirectory[0])
18330           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18331                 Col2Text(0),   // whitePieceColor
18332                 Col2Text(1) ); // blackPieceColor
18333       }
18334       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18335                 Col2Text(4),   // highlightSquareColor
18336                 Col2Text(5) ); // premoveHighlightColor
18337         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18338         if(insert != q) insert[-1] = NULLCHAR;
18339         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18340         if(q)   free(q);
18341     }
18342     ActivateTheme(FALSE);
18343 }