Merge branch 'v4.7.x' into master
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "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_STEALMATE:
8057               case MT_STALEMATE:
8058               case MT_STAINMATE:
8059                 reason = "Xboard adjudication: Stalemate";
8060                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8061                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8062                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8063                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8064                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8065                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8066                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8067                                                                         EP_CHECKMATE : EP_WINS);
8068                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8069                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8070                 }
8071                 break;
8072               case MT_CHECKMATE:
8073                 reason = "Xboard adjudication: Checkmate";
8074                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8075                 if(gameInfo.variant == VariantShogi) {
8076                     if(forwardMostMove > backwardMostMove
8077                        && moveList[forwardMostMove-1][1] == '@'
8078                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8079                         reason = "XBoard adjudication: pawn-drop mate";
8080                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8081                     }
8082                 }
8083                 break;
8084             }
8085
8086                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8087                     case EP_STALEMATE:
8088                         result = GameIsDrawn; break;
8089                     case EP_CHECKMATE:
8090                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8091                     case EP_WINS:
8092                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8093                     default:
8094                         result = EndOfFile;
8095                 }
8096                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8097                     if(engineOpponent)
8098                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8099                     GameEnds( result, reason, GE_XBOARD );
8100                     return 1;
8101                 }
8102
8103                 /* Next absolutely insufficient mating material. */
8104                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8105                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8106                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8107
8108                      /* always flag draws, for judging claims */
8109                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8110
8111                      if(canAdjudicate && appData.materialDraws) {
8112                          /* but only adjudicate them if adjudication enabled */
8113                          if(engineOpponent) {
8114                            SendToProgram("force\n", engineOpponent); // suppress reply
8115                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8116                          }
8117                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8118                          return 1;
8119                      }
8120                 }
8121
8122                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8123                 if(gameInfo.variant == VariantXiangqi ?
8124                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8125                  : nrW + nrB == 4 &&
8126                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8127                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8128                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8129                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8130                    ) ) {
8131                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8132                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8133                           if(engineOpponent) {
8134                             SendToProgram("force\n", engineOpponent); // suppress reply
8135                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8136                           }
8137                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8138                           return 1;
8139                      }
8140                 } else moveCount = 6;
8141             }
8142
8143         // Repetition draws and 50-move rule can be applied independently of legality testing
8144
8145                 /* Check for rep-draws */
8146                 count = 0;
8147                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8148                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8149                 for(k = forwardMostMove-2;
8150                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8151                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8152                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8153                     k-=2)
8154                 {   int rights=0;
8155                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8156                         /* compare castling rights */
8157                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8158                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8159                                 rights++; /* King lost rights, while rook still had them */
8160                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8161                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8162                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8163                                    rights++; /* but at least one rook lost them */
8164                         }
8165                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8166                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8167                                 rights++;
8168                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8169                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8170                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8171                                    rights++;
8172                         }
8173                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8174                             && appData.drawRepeats > 1) {
8175                              /* adjudicate after user-specified nr of repeats */
8176                              int result = GameIsDrawn;
8177                              char *details = "XBoard adjudication: repetition draw";
8178                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8179                                 // [HGM] xiangqi: check for forbidden perpetuals
8180                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8181                                 for(m=forwardMostMove; m>k; m-=2) {
8182                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8183                                         ourPerpetual = 0; // the current mover did not always check
8184                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8185                                         hisPerpetual = 0; // the opponent did not always check
8186                                 }
8187                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8188                                                                         ourPerpetual, hisPerpetual);
8189                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8190                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8191                                     details = "Xboard adjudication: perpetual checking";
8192                                 } else
8193                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8194                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8195                                 } else
8196                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8197                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8198                                         result = BlackWins;
8199                                         details = "Xboard adjudication: repetition";
8200                                     }
8201                                 } else // it must be XQ
8202                                 // Now check for perpetual chases
8203                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8204                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8205                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8206                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8207                                         static char resdet[MSG_SIZ];
8208                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8209                                         details = resdet;
8210                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8211                                     } else
8212                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8213                                         break; // Abort repetition-checking loop.
8214                                 }
8215                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8216                              }
8217                              if(engineOpponent) {
8218                                SendToProgram("force\n", engineOpponent); // suppress reply
8219                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8220                              }
8221                              GameEnds( result, details, GE_XBOARD );
8222                              return 1;
8223                         }
8224                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8225                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8226                     }
8227                 }
8228
8229                 /* Now we test for 50-move draws. Determine ply count */
8230                 count = forwardMostMove;
8231                 /* look for last irreversble move */
8232                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8233                     count--;
8234                 /* if we hit starting position, add initial plies */
8235                 if( count == backwardMostMove )
8236                     count -= initialRulePlies;
8237                 count = forwardMostMove - count;
8238                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8239                         // adjust reversible move counter for checks in Xiangqi
8240                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8241                         if(i < backwardMostMove) i = backwardMostMove;
8242                         while(i <= forwardMostMove) {
8243                                 lastCheck = inCheck; // check evasion does not count
8244                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8245                                 if(inCheck || lastCheck) count--; // check does not count
8246                                 i++;
8247                         }
8248                 }
8249                 if( count >= 100)
8250                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8251                          /* this is used to judge if draw claims are legal */
8252                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8253                          if(engineOpponent) {
8254                            SendToProgram("force\n", engineOpponent); // suppress reply
8255                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8256                          }
8257                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8258                          return 1;
8259                 }
8260
8261                 /* if draw offer is pending, treat it as a draw claim
8262                  * when draw condition present, to allow engines a way to
8263                  * claim draws before making their move to avoid a race
8264                  * condition occurring after their move
8265                  */
8266                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8267                          char *p = NULL;
8268                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8269                              p = "Draw claim: 50-move rule";
8270                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8271                              p = "Draw claim: 3-fold repetition";
8272                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8273                              p = "Draw claim: insufficient mating material";
8274                          if( p != NULL && canAdjudicate) {
8275                              if(engineOpponent) {
8276                                SendToProgram("force\n", engineOpponent); // suppress reply
8277                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8278                              }
8279                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8280                              return 1;
8281                          }
8282                 }
8283
8284                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8285                     if(engineOpponent) {
8286                       SendToProgram("force\n", engineOpponent); // suppress reply
8287                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8288                     }
8289                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8290                     return 1;
8291                 }
8292         return 0;
8293 }
8294
8295 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8296 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8297 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8298
8299 static int
8300 BitbaseProbe ()
8301 {
8302     int pieces[10], squares[10], cnt=0, r, f, res;
8303     static int loaded;
8304     static PPROBE_EGBB probeBB;
8305     if(!appData.testLegality) return 10;
8306     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8307     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8308     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8309     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8310         ChessSquare piece = boards[forwardMostMove][r][f];
8311         int black = (piece >= BlackPawn);
8312         int type = piece - black*BlackPawn;
8313         if(piece == EmptySquare) continue;
8314         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8315         if(type == WhiteKing) type = WhiteQueen + 1;
8316         type = egbbCode[type];
8317         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8318         pieces[cnt] = type + black*6;
8319         if(++cnt > 5) return 11;
8320     }
8321     pieces[cnt] = squares[cnt] = 0;
8322     // probe EGBB
8323     if(loaded == 2) return 13; // loading failed before
8324     if(loaded == 0) {
8325         loaded = 2; // prepare for failure
8326         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8327         HMODULE lib;
8328         PLOAD_EGBB loadBB;
8329         if(!path) return 13; // no egbb installed
8330         strncpy(buf, path + 8, MSG_SIZ);
8331         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8332         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8333         lib = LoadLibrary(buf);
8334         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8335         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8336         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8337         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8338         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8339         loaded = 1; // success!
8340     }
8341     res = probeBB(forwardMostMove & 1, pieces, squares);
8342     return res > 0 ? 1 : res < 0 ? -1 : 0;
8343 }
8344
8345 char *
8346 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8347 {   // [HGM] book: this routine intercepts moves to simulate book replies
8348     char *bookHit = NULL;
8349
8350     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8351         char buf[MSG_SIZ];
8352         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8353         SendToProgram(buf, cps);
8354     }
8355     //first determine if the incoming move brings opponent into his book
8356     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8357         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8358     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8359     if(bookHit != NULL && !cps->bookSuspend) {
8360         // make sure opponent is not going to reply after receiving move to book position
8361         SendToProgram("force\n", cps);
8362         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8363     }
8364     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8365     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8366     // now arrange restart after book miss
8367     if(bookHit) {
8368         // after a book hit we never send 'go', and the code after the call to this routine
8369         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8370         char buf[MSG_SIZ], *move = bookHit;
8371         if(cps->useSAN) {
8372             int fromX, fromY, toX, toY;
8373             char promoChar;
8374             ChessMove moveType;
8375             move = buf + 30;
8376             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8377                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8378                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8379                                     PosFlags(forwardMostMove),
8380                                     fromY, fromX, toY, toX, promoChar, move);
8381             } else {
8382                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8383                 bookHit = NULL;
8384             }
8385         }
8386         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8387         SendToProgram(buf, cps);
8388         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8389     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8390         SendToProgram("go\n", cps);
8391         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8392     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8393         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8394             SendToProgram("go\n", cps);
8395         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8396     }
8397     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8398 }
8399
8400 int
8401 LoadError (char *errmess, ChessProgramState *cps)
8402 {   // unloads engine and switches back to -ncp mode if it was first
8403     if(cps->initDone) return FALSE;
8404     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8405     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8406     cps->pr = NoProc;
8407     if(cps == &first) {
8408         appData.noChessProgram = TRUE;
8409         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8410         gameMode = BeginningOfGame; ModeHighlight();
8411         SetNCPMode();
8412     }
8413     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8414     DisplayMessage("", ""); // erase waiting message
8415     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8416     return TRUE;
8417 }
8418
8419 char *savedMessage;
8420 ChessProgramState *savedState;
8421 void
8422 DeferredBookMove (void)
8423 {
8424         if(savedState->lastPing != savedState->lastPong)
8425                     ScheduleDelayedEvent(DeferredBookMove, 10);
8426         else
8427         HandleMachineMove(savedMessage, savedState);
8428 }
8429
8430 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8431 static ChessProgramState *stalledEngine;
8432 static char stashedInputMove[MSG_SIZ];
8433
8434 void
8435 HandleMachineMove (char *message, ChessProgramState *cps)
8436 {
8437     static char firstLeg[20];
8438     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8439     char realname[MSG_SIZ];
8440     int fromX, fromY, toX, toY;
8441     ChessMove moveType;
8442     char promoChar, roar;
8443     char *p, *pv=buf1;
8444     int machineWhite, oldError;
8445     char *bookHit;
8446
8447     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8448         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8449         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8450             DisplayError(_("Invalid pairing from pairing engine"), 0);
8451             return;
8452         }
8453         pairingReceived = 1;
8454         NextMatchGame();
8455         return; // Skim the pairing messages here.
8456     }
8457
8458     oldError = cps->userError; cps->userError = 0;
8459
8460 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8461     /*
8462      * Kludge to ignore BEL characters
8463      */
8464     while (*message == '\007') message++;
8465
8466     /*
8467      * [HGM] engine debug message: ignore lines starting with '#' character
8468      */
8469     if(cps->debug && *message == '#') return;
8470
8471     /*
8472      * Look for book output
8473      */
8474     if (cps == &first && bookRequested) {
8475         if (message[0] == '\t' || message[0] == ' ') {
8476             /* Part of the book output is here; append it */
8477             strcat(bookOutput, message);
8478             strcat(bookOutput, "  \n");
8479             return;
8480         } else if (bookOutput[0] != NULLCHAR) {
8481             /* All of book output has arrived; display it */
8482             char *p = bookOutput;
8483             while (*p != NULLCHAR) {
8484                 if (*p == '\t') *p = ' ';
8485                 p++;
8486             }
8487             DisplayInformation(bookOutput);
8488             bookRequested = FALSE;
8489             /* Fall through to parse the current output */
8490         }
8491     }
8492
8493     /*
8494      * Look for machine move.
8495      */
8496     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8497         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8498     {
8499         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8500             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8501             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8502             stalledEngine = cps;
8503             if(appData.ponderNextMove) { // bring opponent out of ponder
8504                 if(gameMode == TwoMachinesPlay) {
8505                     if(cps->other->pause)
8506                         PauseEngine(cps->other);
8507                     else
8508                         SendToProgram("easy\n", cps->other);
8509                 }
8510             }
8511             StopClocks();
8512             return;
8513         }
8514
8515         /* This method is only useful on engines that support ping */
8516         if (cps->lastPing != cps->lastPong) {
8517           if (gameMode == BeginningOfGame) {
8518             /* Extra move from before last new; ignore */
8519             if (appData.debugMode) {
8520                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8521             }
8522           } else {
8523             if (appData.debugMode) {
8524                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8525                         cps->which, gameMode);
8526             }
8527
8528             SendToProgram("undo\n", cps);
8529           }
8530           return;
8531         }
8532
8533         switch (gameMode) {
8534           case BeginningOfGame:
8535             /* Extra move from before last reset; ignore */
8536             if (appData.debugMode) {
8537                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8538             }
8539             return;
8540
8541           case EndOfGame:
8542           case IcsIdle:
8543           default:
8544             /* Extra move after we tried to stop.  The mode test is
8545                not a reliable way of detecting this problem, but it's
8546                the best we can do on engines that don't support ping.
8547             */
8548             if (appData.debugMode) {
8549                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8550                         cps->which, gameMode);
8551             }
8552             SendToProgram("undo\n", cps);
8553             return;
8554
8555           case MachinePlaysWhite:
8556           case IcsPlayingWhite:
8557             machineWhite = TRUE;
8558             break;
8559
8560           case MachinePlaysBlack:
8561           case IcsPlayingBlack:
8562             machineWhite = FALSE;
8563             break;
8564
8565           case TwoMachinesPlay:
8566             machineWhite = (cps->twoMachinesColor[0] == 'w');
8567             break;
8568         }
8569         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8570             if (appData.debugMode) {
8571                 fprintf(debugFP,
8572                         "Ignoring move out of turn by %s, gameMode %d"
8573                         ", forwardMost %d\n",
8574                         cps->which, gameMode, forwardMostMove);
8575             }
8576             return;
8577         }
8578
8579         if(cps->alphaRank) AlphaRank(machineMove, 4);
8580
8581         // [HGM] lion: (some very limited) support for Alien protocol
8582         killX = killY = -1;
8583         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8584             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8585             return;
8586         } else if(firstLeg[0]) { // there was a previous leg;
8587             // only support case where same piece makes two step (and don't even test that!)
8588             char buf[20], *p = machineMove+1, *q = buf+1, f;
8589             safeStrCpy(buf, machineMove, 20);
8590             while(isdigit(*q)) q++; // find start of to-square
8591             safeStrCpy(machineMove, firstLeg, 20);
8592             while(isdigit(*p)) p++;
8593             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8594             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8595             firstLeg[0] = NULLCHAR;
8596         }
8597
8598         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8599                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8600             /* Machine move could not be parsed; ignore it. */
8601           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8602                     machineMove, _(cps->which));
8603             DisplayMoveError(buf1);
8604             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8605                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8606             if (gameMode == TwoMachinesPlay) {
8607               GameEnds(machineWhite ? BlackWins : WhiteWins,
8608                        buf1, GE_XBOARD);
8609             }
8610             return;
8611         }
8612
8613         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8614         /* So we have to redo legality test with true e.p. status here,  */
8615         /* to make sure an illegal e.p. capture does not slip through,   */
8616         /* to cause a forfeit on a justified illegal-move complaint      */
8617         /* of the opponent.                                              */
8618         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8619            ChessMove moveType;
8620            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8621                              fromY, fromX, toY, toX, promoChar);
8622             if(moveType == IllegalMove) {
8623               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8624                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8625                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8626                            buf1, GE_XBOARD);
8627                 return;
8628            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8629            /* [HGM] Kludge to handle engines that send FRC-style castling
8630               when they shouldn't (like TSCP-Gothic) */
8631            switch(moveType) {
8632              case WhiteASideCastleFR:
8633              case BlackASideCastleFR:
8634                toX+=2;
8635                currentMoveString[2]++;
8636                break;
8637              case WhiteHSideCastleFR:
8638              case BlackHSideCastleFR:
8639                toX--;
8640                currentMoveString[2]--;
8641                break;
8642              default: ; // nothing to do, but suppresses warning of pedantic compilers
8643            }
8644         }
8645         hintRequested = FALSE;
8646         lastHint[0] = NULLCHAR;
8647         bookRequested = FALSE;
8648         /* Program may be pondering now */
8649         cps->maybeThinking = TRUE;
8650         if (cps->sendTime == 2) cps->sendTime = 1;
8651         if (cps->offeredDraw) cps->offeredDraw--;
8652
8653         /* [AS] Save move info*/
8654         pvInfoList[ forwardMostMove ].score = programStats.score;
8655         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8656         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8657
8658         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8659
8660         /* Test suites abort the 'game' after one move */
8661         if(*appData.finger) {
8662            static FILE *f;
8663            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8664            if(!f) f = fopen(appData.finger, "w");
8665            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8666            else { DisplayFatalError("Bad output file", errno, 0); return; }
8667            free(fen);
8668            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8669         }
8670
8671         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8672         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8673             int count = 0;
8674
8675             while( count < adjudicateLossPlies ) {
8676                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8677
8678                 if( count & 1 ) {
8679                     score = -score; /* Flip score for winning side */
8680                 }
8681
8682                 if( score > adjudicateLossThreshold ) {
8683                     break;
8684                 }
8685
8686                 count++;
8687             }
8688
8689             if( count >= adjudicateLossPlies ) {
8690                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8691
8692                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8693                     "Xboard adjudication",
8694                     GE_XBOARD );
8695
8696                 return;
8697             }
8698         }
8699
8700         if(Adjudicate(cps)) {
8701             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8702             return; // [HGM] adjudicate: for all automatic game ends
8703         }
8704
8705 #if ZIPPY
8706         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8707             first.initDone) {
8708           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8709                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8710                 SendToICS("draw ");
8711                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8712           }
8713           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8714           ics_user_moved = 1;
8715           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8716                 char buf[3*MSG_SIZ];
8717
8718                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8719                         programStats.score / 100.,
8720                         programStats.depth,
8721                         programStats.time / 100.,
8722                         (unsigned int)programStats.nodes,
8723                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8724                         programStats.movelist);
8725                 SendToICS(buf);
8726 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8727           }
8728         }
8729 #endif
8730
8731         /* [AS] Clear stats for next move */
8732         ClearProgramStats();
8733         thinkOutput[0] = NULLCHAR;
8734         hiddenThinkOutputState = 0;
8735
8736         bookHit = NULL;
8737         if (gameMode == TwoMachinesPlay) {
8738             /* [HGM] relaying draw offers moved to after reception of move */
8739             /* and interpreting offer as claim if it brings draw condition */
8740             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8741                 SendToProgram("draw\n", cps->other);
8742             }
8743             if (cps->other->sendTime) {
8744                 SendTimeRemaining(cps->other,
8745                                   cps->other->twoMachinesColor[0] == 'w');
8746             }
8747             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8748             if (firstMove && !bookHit) {
8749                 firstMove = FALSE;
8750                 if (cps->other->useColors) {
8751                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8752                 }
8753                 SendToProgram("go\n", cps->other);
8754             }
8755             cps->other->maybeThinking = TRUE;
8756         }
8757
8758         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8759
8760         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8761
8762         if (!pausing && appData.ringBellAfterMoves) {
8763             if(!roar) RingBell();
8764         }
8765
8766         /*
8767          * Reenable menu items that were disabled while
8768          * machine was thinking
8769          */
8770         if (gameMode != TwoMachinesPlay)
8771             SetUserThinkingEnables();
8772
8773         // [HGM] book: after book hit opponent has received move and is now in force mode
8774         // force the book reply into it, and then fake that it outputted this move by jumping
8775         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8776         if(bookHit) {
8777                 static char bookMove[MSG_SIZ]; // a bit generous?
8778
8779                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8780                 strcat(bookMove, bookHit);
8781                 message = bookMove;
8782                 cps = cps->other;
8783                 programStats.nodes = programStats.depth = programStats.time =
8784                 programStats.score = programStats.got_only_move = 0;
8785                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8786
8787                 if(cps->lastPing != cps->lastPong) {
8788                     savedMessage = message; // args for deferred call
8789                     savedState = cps;
8790                     ScheduleDelayedEvent(DeferredBookMove, 10);
8791                     return;
8792                 }
8793                 goto FakeBookMove;
8794         }
8795
8796         return;
8797     }
8798
8799     /* Set special modes for chess engines.  Later something general
8800      *  could be added here; for now there is just one kludge feature,
8801      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8802      *  when "xboard" is given as an interactive command.
8803      */
8804     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8805         cps->useSigint = FALSE;
8806         cps->useSigterm = FALSE;
8807     }
8808     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8809       ParseFeatures(message+8, cps);
8810       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8811     }
8812
8813     if (!strncmp(message, "setup ", 6) && 
8814         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8815           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8816                                         ) { // [HGM] allow first engine to define opening position
8817       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8818       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8819       *buf = NULLCHAR;
8820       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8821       if(startedFromSetupPosition) return;
8822       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8823       if(dummy >= 3) {
8824         while(message[s] && message[s++] != ' ');
8825         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8826            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8827             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8828             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8829           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8830           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8831         }
8832       }
8833       ParseFEN(boards[0], &dummy, message+s, FALSE);
8834       DrawPosition(TRUE, boards[0]);
8835       startedFromSetupPosition = TRUE;
8836       return;
8837     }
8838     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8839      * want this, I was asked to put it in, and obliged.
8840      */
8841     if (!strncmp(message, "setboard ", 9)) {
8842         Board initial_position;
8843
8844         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8845
8846         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8847             DisplayError(_("Bad FEN received from engine"), 0);
8848             return ;
8849         } else {
8850            Reset(TRUE, FALSE);
8851            CopyBoard(boards[0], initial_position);
8852            initialRulePlies = FENrulePlies;
8853            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8854            else gameMode = MachinePlaysBlack;
8855            DrawPosition(FALSE, boards[currentMove]);
8856         }
8857         return;
8858     }
8859
8860     /*
8861      * Look for communication commands
8862      */
8863     if (!strncmp(message, "telluser ", 9)) {
8864         if(message[9] == '\\' && message[10] == '\\')
8865             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8866         PlayTellSound();
8867         DisplayNote(message + 9);
8868         return;
8869     }
8870     if (!strncmp(message, "tellusererror ", 14)) {
8871         cps->userError = 1;
8872         if(message[14] == '\\' && message[15] == '\\')
8873             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8874         PlayTellSound();
8875         DisplayError(message + 14, 0);
8876         return;
8877     }
8878     if (!strncmp(message, "tellopponent ", 13)) {
8879       if (appData.icsActive) {
8880         if (loggedOn) {
8881           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8882           SendToICS(buf1);
8883         }
8884       } else {
8885         DisplayNote(message + 13);
8886       }
8887       return;
8888     }
8889     if (!strncmp(message, "tellothers ", 11)) {
8890       if (appData.icsActive) {
8891         if (loggedOn) {
8892           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8893           SendToICS(buf1);
8894         }
8895       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8896       return;
8897     }
8898     if (!strncmp(message, "tellall ", 8)) {
8899       if (appData.icsActive) {
8900         if (loggedOn) {
8901           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8902           SendToICS(buf1);
8903         }
8904       } else {
8905         DisplayNote(message + 8);
8906       }
8907       return;
8908     }
8909     if (strncmp(message, "warning", 7) == 0) {
8910         /* Undocumented feature, use tellusererror in new code */
8911         DisplayError(message, 0);
8912         return;
8913     }
8914     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8915         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8916         strcat(realname, " query");
8917         AskQuestion(realname, buf2, buf1, cps->pr);
8918         return;
8919     }
8920     /* Commands from the engine directly to ICS.  We don't allow these to be
8921      *  sent until we are logged on. Crafty kibitzes have been known to
8922      *  interfere with the login process.
8923      */
8924     if (loggedOn) {
8925         if (!strncmp(message, "tellics ", 8)) {
8926             SendToICS(message + 8);
8927             SendToICS("\n");
8928             return;
8929         }
8930         if (!strncmp(message, "tellicsnoalias ", 15)) {
8931             SendToICS(ics_prefix);
8932             SendToICS(message + 15);
8933             SendToICS("\n");
8934             return;
8935         }
8936         /* The following are for backward compatibility only */
8937         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8938             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8939             SendToICS(ics_prefix);
8940             SendToICS(message);
8941             SendToICS("\n");
8942             return;
8943         }
8944     }
8945     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8946         if(initPing == cps->lastPong) {
8947             if(gameInfo.variant == VariantUnknown) {
8948                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
8949                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
8950                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
8951             }
8952             initPing = -1;
8953         }
8954         return;
8955     }
8956     if(!strncmp(message, "highlight ", 10)) {
8957         if(appData.testLegality && appData.markers) return;
8958         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
8959         return;
8960     }
8961     if(!strncmp(message, "click ", 6)) {
8962         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
8963         if(appData.testLegality || !appData.oneClick) return;
8964         sscanf(message+6, "%c%d%c", &f, &y, &c);
8965         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
8966         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
8967         x = x*squareSize + (x+1)*lineGap + squareSize/2;
8968         y = y*squareSize + (y+1)*lineGap + squareSize/2;
8969         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
8970         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
8971             LeftClick(Release, lastLeftX, lastLeftY);
8972         controlKey  = (c == ',');
8973         LeftClick(Press, x, y);
8974         LeftClick(Release, x, y);
8975         first.highlight = f;
8976         return;
8977     }
8978     /*
8979      * If the move is illegal, cancel it and redraw the board.
8980      * Also deal with other error cases.  Matching is rather loose
8981      * here to accommodate engines written before the spec.
8982      */
8983     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8984         strncmp(message, "Error", 5) == 0) {
8985         if (StrStr(message, "name") ||
8986             StrStr(message, "rating") || StrStr(message, "?") ||
8987             StrStr(message, "result") || StrStr(message, "board") ||
8988             StrStr(message, "bk") || StrStr(message, "computer") ||
8989             StrStr(message, "variant") || StrStr(message, "hint") ||
8990             StrStr(message, "random") || StrStr(message, "depth") ||
8991             StrStr(message, "accepted")) {
8992             return;
8993         }
8994         if (StrStr(message, "protover")) {
8995           /* Program is responding to input, so it's apparently done
8996              initializing, and this error message indicates it is
8997              protocol version 1.  So we don't need to wait any longer
8998              for it to initialize and send feature commands. */
8999           FeatureDone(cps, 1);
9000           cps->protocolVersion = 1;
9001           return;
9002         }
9003         cps->maybeThinking = FALSE;
9004
9005         if (StrStr(message, "draw")) {
9006             /* Program doesn't have "draw" command */
9007             cps->sendDrawOffers = 0;
9008             return;
9009         }
9010         if (cps->sendTime != 1 &&
9011             (StrStr(message, "time") || StrStr(message, "otim"))) {
9012           /* Program apparently doesn't have "time" or "otim" command */
9013           cps->sendTime = 0;
9014           return;
9015         }
9016         if (StrStr(message, "analyze")) {
9017             cps->analysisSupport = FALSE;
9018             cps->analyzing = FALSE;
9019 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9020             EditGameEvent(); // [HGM] try to preserve loaded game
9021             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9022             DisplayError(buf2, 0);
9023             return;
9024         }
9025         if (StrStr(message, "(no matching move)st")) {
9026           /* Special kludge for GNU Chess 4 only */
9027           cps->stKludge = TRUE;
9028           SendTimeControl(cps, movesPerSession, timeControl,
9029                           timeIncrement, appData.searchDepth,
9030                           searchTime);
9031           return;
9032         }
9033         if (StrStr(message, "(no matching move)sd")) {
9034           /* Special kludge for GNU Chess 4 only */
9035           cps->sdKludge = TRUE;
9036           SendTimeControl(cps, movesPerSession, timeControl,
9037                           timeIncrement, appData.searchDepth,
9038                           searchTime);
9039           return;
9040         }
9041         if (!StrStr(message, "llegal")) {
9042             return;
9043         }
9044         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9045             gameMode == IcsIdle) return;
9046         if (forwardMostMove <= backwardMostMove) return;
9047         if (pausing) PauseEvent();
9048       if(appData.forceIllegal) {
9049             // [HGM] illegal: machine refused move; force position after move into it
9050           SendToProgram("force\n", cps);
9051           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9052                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9053                 // when black is to move, while there might be nothing on a2 or black
9054                 // might already have the move. So send the board as if white has the move.
9055                 // But first we must change the stm of the engine, as it refused the last move
9056                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9057                 if(WhiteOnMove(forwardMostMove)) {
9058                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9059                     SendBoard(cps, forwardMostMove); // kludgeless board
9060                 } else {
9061                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9062                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9063                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9064                 }
9065           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9066             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9067                  gameMode == TwoMachinesPlay)
9068               SendToProgram("go\n", cps);
9069             return;
9070       } else
9071         if (gameMode == PlayFromGameFile) {
9072             /* Stop reading this game file */
9073             gameMode = EditGame;
9074             ModeHighlight();
9075         }
9076         /* [HGM] illegal-move claim should forfeit game when Xboard */
9077         /* only passes fully legal moves                            */
9078         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9079             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9080                                 "False illegal-move claim", GE_XBOARD );
9081             return; // do not take back move we tested as valid
9082         }
9083         currentMove = forwardMostMove-1;
9084         DisplayMove(currentMove-1); /* before DisplayMoveError */
9085         SwitchClocks(forwardMostMove-1); // [HGM] race
9086         DisplayBothClocks();
9087         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9088                 parseList[currentMove], _(cps->which));
9089         DisplayMoveError(buf1);
9090         DrawPosition(FALSE, boards[currentMove]);
9091
9092         SetUserThinkingEnables();
9093         return;
9094     }
9095     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9096         /* Program has a broken "time" command that
9097            outputs a string not ending in newline.
9098            Don't use it. */
9099         cps->sendTime = 0;
9100     }
9101
9102     /*
9103      * If chess program startup fails, exit with an error message.
9104      * Attempts to recover here are futile. [HGM] Well, we try anyway
9105      */
9106     if ((StrStr(message, "unknown host") != NULL)
9107         || (StrStr(message, "No remote directory") != NULL)
9108         || (StrStr(message, "not found") != NULL)
9109         || (StrStr(message, "No such file") != NULL)
9110         || (StrStr(message, "can't alloc") != NULL)
9111         || (StrStr(message, "Permission denied") != NULL)) {
9112
9113         cps->maybeThinking = FALSE;
9114         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9115                 _(cps->which), cps->program, cps->host, message);
9116         RemoveInputSource(cps->isr);
9117         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9118             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9119             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9120         }
9121         return;
9122     }
9123
9124     /*
9125      * Look for hint output
9126      */
9127     if (sscanf(message, "Hint: %s", buf1) == 1) {
9128         if (cps == &first && hintRequested) {
9129             hintRequested = FALSE;
9130             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9131                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9132                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9133                                     PosFlags(forwardMostMove),
9134                                     fromY, fromX, toY, toX, promoChar, buf1);
9135                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9136                 DisplayInformation(buf2);
9137             } else {
9138                 /* Hint move could not be parsed!? */
9139               snprintf(buf2, sizeof(buf2),
9140                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9141                         buf1, _(cps->which));
9142                 DisplayError(buf2, 0);
9143             }
9144         } else {
9145           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9146         }
9147         return;
9148     }
9149
9150     /*
9151      * Ignore other messages if game is not in progress
9152      */
9153     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9154         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9155
9156     /*
9157      * look for win, lose, draw, or draw offer
9158      */
9159     if (strncmp(message, "1-0", 3) == 0) {
9160         char *p, *q, *r = "";
9161         p = strchr(message, '{');
9162         if (p) {
9163             q = strchr(p, '}');
9164             if (q) {
9165                 *q = NULLCHAR;
9166                 r = p + 1;
9167             }
9168         }
9169         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9170         return;
9171     } else if (strncmp(message, "0-1", 3) == 0) {
9172         char *p, *q, *r = "";
9173         p = strchr(message, '{');
9174         if (p) {
9175             q = strchr(p, '}');
9176             if (q) {
9177                 *q = NULLCHAR;
9178                 r = p + 1;
9179             }
9180         }
9181         /* Kludge for Arasan 4.1 bug */
9182         if (strcmp(r, "Black resigns") == 0) {
9183             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9184             return;
9185         }
9186         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9187         return;
9188     } else if (strncmp(message, "1/2", 3) == 0) {
9189         char *p, *q, *r = "";
9190         p = strchr(message, '{');
9191         if (p) {
9192             q = strchr(p, '}');
9193             if (q) {
9194                 *q = NULLCHAR;
9195                 r = p + 1;
9196             }
9197         }
9198
9199         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9200         return;
9201
9202     } else if (strncmp(message, "White resign", 12) == 0) {
9203         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9204         return;
9205     } else if (strncmp(message, "Black resign", 12) == 0) {
9206         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9207         return;
9208     } else if (strncmp(message, "White matches", 13) == 0 ||
9209                strncmp(message, "Black matches", 13) == 0   ) {
9210         /* [HGM] ignore GNUShogi noises */
9211         return;
9212     } else if (strncmp(message, "White", 5) == 0 &&
9213                message[5] != '(' &&
9214                StrStr(message, "Black") == NULL) {
9215         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9216         return;
9217     } else if (strncmp(message, "Black", 5) == 0 &&
9218                message[5] != '(') {
9219         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9220         return;
9221     } else if (strcmp(message, "resign") == 0 ||
9222                strcmp(message, "computer resigns") == 0) {
9223         switch (gameMode) {
9224           case MachinePlaysBlack:
9225           case IcsPlayingBlack:
9226             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9227             break;
9228           case MachinePlaysWhite:
9229           case IcsPlayingWhite:
9230             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9231             break;
9232           case TwoMachinesPlay:
9233             if (cps->twoMachinesColor[0] == 'w')
9234               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9235             else
9236               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9237             break;
9238           default:
9239             /* can't happen */
9240             break;
9241         }
9242         return;
9243     } else if (strncmp(message, "opponent mates", 14) == 0) {
9244         switch (gameMode) {
9245           case MachinePlaysBlack:
9246           case IcsPlayingBlack:
9247             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9248             break;
9249           case MachinePlaysWhite:
9250           case IcsPlayingWhite:
9251             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9252             break;
9253           case TwoMachinesPlay:
9254             if (cps->twoMachinesColor[0] == 'w')
9255               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9256             else
9257               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9258             break;
9259           default:
9260             /* can't happen */
9261             break;
9262         }
9263         return;
9264     } else if (strncmp(message, "computer mates", 14) == 0) {
9265         switch (gameMode) {
9266           case MachinePlaysBlack:
9267           case IcsPlayingBlack:
9268             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9269             break;
9270           case MachinePlaysWhite:
9271           case IcsPlayingWhite:
9272             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9273             break;
9274           case TwoMachinesPlay:
9275             if (cps->twoMachinesColor[0] == 'w')
9276               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9277             else
9278               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9279             break;
9280           default:
9281             /* can't happen */
9282             break;
9283         }
9284         return;
9285     } else if (strncmp(message, "checkmate", 9) == 0) {
9286         if (WhiteOnMove(forwardMostMove)) {
9287             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9288         } else {
9289             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9290         }
9291         return;
9292     } else if (strstr(message, "Draw") != NULL ||
9293                strstr(message, "game is a draw") != NULL) {
9294         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9295         return;
9296     } else if (strstr(message, "offer") != NULL &&
9297                strstr(message, "draw") != NULL) {
9298 #if ZIPPY
9299         if (appData.zippyPlay && first.initDone) {
9300             /* Relay offer to ICS */
9301             SendToICS(ics_prefix);
9302             SendToICS("draw\n");
9303         }
9304 #endif
9305         cps->offeredDraw = 2; /* valid until this engine moves twice */
9306         if (gameMode == TwoMachinesPlay) {
9307             if (cps->other->offeredDraw) {
9308                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9309             /* [HGM] in two-machine mode we delay relaying draw offer      */
9310             /* until after we also have move, to see if it is really claim */
9311             }
9312         } else if (gameMode == MachinePlaysWhite ||
9313                    gameMode == MachinePlaysBlack) {
9314           if (userOfferedDraw) {
9315             DisplayInformation(_("Machine accepts your draw offer"));
9316             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9317           } else {
9318             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9319           }
9320         }
9321     }
9322
9323
9324     /*
9325      * Look for thinking output
9326      */
9327     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9328           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9329                                 ) {
9330         int plylev, mvleft, mvtot, curscore, time;
9331         char mvname[MOVE_LEN];
9332         u64 nodes; // [DM]
9333         char plyext;
9334         int ignore = FALSE;
9335         int prefixHint = FALSE;
9336         mvname[0] = NULLCHAR;
9337
9338         switch (gameMode) {
9339           case MachinePlaysBlack:
9340           case IcsPlayingBlack:
9341             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9342             break;
9343           case MachinePlaysWhite:
9344           case IcsPlayingWhite:
9345             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9346             break;
9347           case AnalyzeMode:
9348           case AnalyzeFile:
9349             break;
9350           case IcsObserving: /* [DM] icsEngineAnalyze */
9351             if (!appData.icsEngineAnalyze) ignore = TRUE;
9352             break;
9353           case TwoMachinesPlay:
9354             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9355                 ignore = TRUE;
9356             }
9357             break;
9358           default:
9359             ignore = TRUE;
9360             break;
9361         }
9362
9363         if (!ignore) {
9364             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9365             buf1[0] = NULLCHAR;
9366             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9367                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9368
9369                 if (plyext != ' ' && plyext != '\t') {
9370                     time *= 100;
9371                 }
9372
9373                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9374                 if( cps->scoreIsAbsolute &&
9375                     ( gameMode == MachinePlaysBlack ||
9376                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9377                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9378                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9379                      !WhiteOnMove(currentMove)
9380                     ) )
9381                 {
9382                     curscore = -curscore;
9383                 }
9384
9385                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9386
9387                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9388                         char buf[MSG_SIZ];
9389                         FILE *f;
9390                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9391                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9392                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9393                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9394                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9395                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9396                                 fclose(f);
9397                         }
9398                         else
9399                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9400                           DisplayError(_("failed writing PV"), 0);
9401                 }
9402
9403                 tempStats.depth = plylev;
9404                 tempStats.nodes = nodes;
9405                 tempStats.time = time;
9406                 tempStats.score = curscore;
9407                 tempStats.got_only_move = 0;
9408
9409                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9410                         int ticklen;
9411
9412                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9413                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9414                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9415                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9416                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9417                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9418                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9419                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9420                 }
9421
9422                 /* Buffer overflow protection */
9423                 if (pv[0] != NULLCHAR) {
9424                     if (strlen(pv) >= sizeof(tempStats.movelist)
9425                         && appData.debugMode) {
9426                         fprintf(debugFP,
9427                                 "PV is too long; using the first %u bytes.\n",
9428                                 (unsigned) sizeof(tempStats.movelist) - 1);
9429                     }
9430
9431                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9432                 } else {
9433                     sprintf(tempStats.movelist, " no PV\n");
9434                 }
9435
9436                 if (tempStats.seen_stat) {
9437                     tempStats.ok_to_send = 1;
9438                 }
9439
9440                 if (strchr(tempStats.movelist, '(') != NULL) {
9441                     tempStats.line_is_book = 1;
9442                     tempStats.nr_moves = 0;
9443                     tempStats.moves_left = 0;
9444                 } else {
9445                     tempStats.line_is_book = 0;
9446                 }
9447
9448                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9449                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9450
9451                 SendProgramStatsToFrontend( cps, &tempStats );
9452
9453                 /*
9454                     [AS] Protect the thinkOutput buffer from overflow... this
9455                     is only useful if buf1 hasn't overflowed first!
9456                 */
9457                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9458                          plylev,
9459                          (gameMode == TwoMachinesPlay ?
9460                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9461                          ((double) curscore) / 100.0,
9462                          prefixHint ? lastHint : "",
9463                          prefixHint ? " " : "" );
9464
9465                 if( buf1[0] != NULLCHAR ) {
9466                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9467
9468                     if( strlen(pv) > max_len ) {
9469                         if( appData.debugMode) {
9470                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9471                         }
9472                         pv[max_len+1] = '\0';
9473                     }
9474
9475                     strcat( thinkOutput, pv);
9476                 }
9477
9478                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9479                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9480                     DisplayMove(currentMove - 1);
9481                 }
9482                 return;
9483
9484             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9485                 /* crafty (9.25+) says "(only move) <move>"
9486                  * if there is only 1 legal move
9487                  */
9488                 sscanf(p, "(only move) %s", buf1);
9489                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9490                 sprintf(programStats.movelist, "%s (only move)", buf1);
9491                 programStats.depth = 1;
9492                 programStats.nr_moves = 1;
9493                 programStats.moves_left = 1;
9494                 programStats.nodes = 1;
9495                 programStats.time = 1;
9496                 programStats.got_only_move = 1;
9497
9498                 /* Not really, but we also use this member to
9499                    mean "line isn't going to change" (Crafty
9500                    isn't searching, so stats won't change) */
9501                 programStats.line_is_book = 1;
9502
9503                 SendProgramStatsToFrontend( cps, &programStats );
9504
9505                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9506                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9507                     DisplayMove(currentMove - 1);
9508                 }
9509                 return;
9510             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9511                               &time, &nodes, &plylev, &mvleft,
9512                               &mvtot, mvname) >= 5) {
9513                 /* The stat01: line is from Crafty (9.29+) in response
9514                    to the "." command */
9515                 programStats.seen_stat = 1;
9516                 cps->maybeThinking = TRUE;
9517
9518                 if (programStats.got_only_move || !appData.periodicUpdates)
9519                   return;
9520
9521                 programStats.depth = plylev;
9522                 programStats.time = time;
9523                 programStats.nodes = nodes;
9524                 programStats.moves_left = mvleft;
9525                 programStats.nr_moves = mvtot;
9526                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9527                 programStats.ok_to_send = 1;
9528                 programStats.movelist[0] = '\0';
9529
9530                 SendProgramStatsToFrontend( cps, &programStats );
9531
9532                 return;
9533
9534             } else if (strncmp(message,"++",2) == 0) {
9535                 /* Crafty 9.29+ outputs this */
9536                 programStats.got_fail = 2;
9537                 return;
9538
9539             } else if (strncmp(message,"--",2) == 0) {
9540                 /* Crafty 9.29+ outputs this */
9541                 programStats.got_fail = 1;
9542                 return;
9543
9544             } else if (thinkOutput[0] != NULLCHAR &&
9545                        strncmp(message, "    ", 4) == 0) {
9546                 unsigned message_len;
9547
9548                 p = message;
9549                 while (*p && *p == ' ') p++;
9550
9551                 message_len = strlen( p );
9552
9553                 /* [AS] Avoid buffer overflow */
9554                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9555                     strcat(thinkOutput, " ");
9556                     strcat(thinkOutput, p);
9557                 }
9558
9559                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9560                     strcat(programStats.movelist, " ");
9561                     strcat(programStats.movelist, p);
9562                 }
9563
9564                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9565                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9566                     DisplayMove(currentMove - 1);
9567                 }
9568                 return;
9569             }
9570         }
9571         else {
9572             buf1[0] = NULLCHAR;
9573
9574             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9575                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9576             {
9577                 ChessProgramStats cpstats;
9578
9579                 if (plyext != ' ' && plyext != '\t') {
9580                     time *= 100;
9581                 }
9582
9583                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9584                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9585                     curscore = -curscore;
9586                 }
9587
9588                 cpstats.depth = plylev;
9589                 cpstats.nodes = nodes;
9590                 cpstats.time = time;
9591                 cpstats.score = curscore;
9592                 cpstats.got_only_move = 0;
9593                 cpstats.movelist[0] = '\0';
9594
9595                 if (buf1[0] != NULLCHAR) {
9596                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9597                 }
9598
9599                 cpstats.ok_to_send = 0;
9600                 cpstats.line_is_book = 0;
9601                 cpstats.nr_moves = 0;
9602                 cpstats.moves_left = 0;
9603
9604                 SendProgramStatsToFrontend( cps, &cpstats );
9605             }
9606         }
9607     }
9608 }
9609
9610
9611 /* Parse a game score from the character string "game", and
9612    record it as the history of the current game.  The game
9613    score is NOT assumed to start from the standard position.
9614    The display is not updated in any way.
9615    */
9616 void
9617 ParseGameHistory (char *game)
9618 {
9619     ChessMove moveType;
9620     int fromX, fromY, toX, toY, boardIndex;
9621     char promoChar;
9622     char *p, *q;
9623     char buf[MSG_SIZ];
9624
9625     if (appData.debugMode)
9626       fprintf(debugFP, "Parsing game history: %s\n", game);
9627
9628     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9629     gameInfo.site = StrSave(appData.icsHost);
9630     gameInfo.date = PGNDate();
9631     gameInfo.round = StrSave("-");
9632
9633     /* Parse out names of players */
9634     while (*game == ' ') game++;
9635     p = buf;
9636     while (*game != ' ') *p++ = *game++;
9637     *p = NULLCHAR;
9638     gameInfo.white = StrSave(buf);
9639     while (*game == ' ') game++;
9640     p = buf;
9641     while (*game != ' ' && *game != '\n') *p++ = *game++;
9642     *p = NULLCHAR;
9643     gameInfo.black = StrSave(buf);
9644
9645     /* Parse moves */
9646     boardIndex = blackPlaysFirst ? 1 : 0;
9647     yynewstr(game);
9648     for (;;) {
9649         yyboardindex = boardIndex;
9650         moveType = (ChessMove) Myylex();
9651         switch (moveType) {
9652           case IllegalMove:             /* maybe suicide chess, etc. */
9653   if (appData.debugMode) {
9654     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9655     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9656     setbuf(debugFP, NULL);
9657   }
9658           case WhitePromotion:
9659           case BlackPromotion:
9660           case WhiteNonPromotion:
9661           case BlackNonPromotion:
9662           case NormalMove:
9663           case FirstLeg:
9664           case WhiteCapturesEnPassant:
9665           case BlackCapturesEnPassant:
9666           case WhiteKingSideCastle:
9667           case WhiteQueenSideCastle:
9668           case BlackKingSideCastle:
9669           case BlackQueenSideCastle:
9670           case WhiteKingSideCastleWild:
9671           case WhiteQueenSideCastleWild:
9672           case BlackKingSideCastleWild:
9673           case BlackQueenSideCastleWild:
9674           /* PUSH Fabien */
9675           case WhiteHSideCastleFR:
9676           case WhiteASideCastleFR:
9677           case BlackHSideCastleFR:
9678           case BlackASideCastleFR:
9679           /* POP Fabien */
9680             fromX = currentMoveString[0] - AAA;
9681             fromY = currentMoveString[1] - ONE;
9682             toX = currentMoveString[2] - AAA;
9683             toY = currentMoveString[3] - ONE;
9684             promoChar = currentMoveString[4];
9685             break;
9686           case WhiteDrop:
9687           case BlackDrop:
9688             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9689             fromX = moveType == WhiteDrop ?
9690               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9691             (int) CharToPiece(ToLower(currentMoveString[0]));
9692             fromY = DROP_RANK;
9693             toX = currentMoveString[2] - AAA;
9694             toY = currentMoveString[3] - ONE;
9695             promoChar = NULLCHAR;
9696             break;
9697           case AmbiguousMove:
9698             /* bug? */
9699             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9700   if (appData.debugMode) {
9701     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9702     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9703     setbuf(debugFP, NULL);
9704   }
9705             DisplayError(buf, 0);
9706             return;
9707           case ImpossibleMove:
9708             /* bug? */
9709             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9710   if (appData.debugMode) {
9711     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9712     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9713     setbuf(debugFP, NULL);
9714   }
9715             DisplayError(buf, 0);
9716             return;
9717           case EndOfFile:
9718             if (boardIndex < backwardMostMove) {
9719                 /* Oops, gap.  How did that happen? */
9720                 DisplayError(_("Gap in move list"), 0);
9721                 return;
9722             }
9723             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9724             if (boardIndex > forwardMostMove) {
9725                 forwardMostMove = boardIndex;
9726             }
9727             return;
9728           case ElapsedTime:
9729             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9730                 strcat(parseList[boardIndex-1], " ");
9731                 strcat(parseList[boardIndex-1], yy_text);
9732             }
9733             continue;
9734           case Comment:
9735           case PGNTag:
9736           case NAG:
9737           default:
9738             /* ignore */
9739             continue;
9740           case WhiteWins:
9741           case BlackWins:
9742           case GameIsDrawn:
9743           case GameUnfinished:
9744             if (gameMode == IcsExamining) {
9745                 if (boardIndex < backwardMostMove) {
9746                     /* Oops, gap.  How did that happen? */
9747                     return;
9748                 }
9749                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9750                 return;
9751             }
9752             gameInfo.result = moveType;
9753             p = strchr(yy_text, '{');
9754             if (p == NULL) p = strchr(yy_text, '(');
9755             if (p == NULL) {
9756                 p = yy_text;
9757                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9758             } else {
9759                 q = strchr(p, *p == '{' ? '}' : ')');
9760                 if (q != NULL) *q = NULLCHAR;
9761                 p++;
9762             }
9763             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9764             gameInfo.resultDetails = StrSave(p);
9765             continue;
9766         }
9767         if (boardIndex >= forwardMostMove &&
9768             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9769             backwardMostMove = blackPlaysFirst ? 1 : 0;
9770             return;
9771         }
9772         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9773                                  fromY, fromX, toY, toX, promoChar,
9774                                  parseList[boardIndex]);
9775         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9776         /* currentMoveString is set as a side-effect of yylex */
9777         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9778         strcat(moveList[boardIndex], "\n");
9779         boardIndex++;
9780         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9781         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9782           case MT_NONE:
9783           case MT_STALEMATE:
9784           default:
9785             break;
9786           case MT_CHECK:
9787             if(!IS_SHOGI(gameInfo.variant))
9788                 strcat(parseList[boardIndex - 1], "+");
9789             break;
9790           case MT_CHECKMATE:
9791           case MT_STAINMATE:
9792             strcat(parseList[boardIndex - 1], "#");
9793             break;
9794         }
9795     }
9796 }
9797
9798
9799 /* Apply a move to the given board  */
9800 void
9801 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9802 {
9803   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9804   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9805
9806     /* [HGM] compute & store e.p. status and castling rights for new position */
9807     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9808
9809       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9810       oldEP = (signed char)board[EP_STATUS];
9811       board[EP_STATUS] = EP_NONE;
9812
9813   if (fromY == DROP_RANK) {
9814         /* must be first */
9815         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9816             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9817             return;
9818         }
9819         piece = board[toY][toX] = (ChessSquare) fromX;
9820   } else {
9821       ChessSquare victim;
9822       int i;
9823
9824       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9825            victim = board[killY][killX],
9826            board[killY][killX] = EmptySquare,
9827            board[EP_STATUS] = EP_CAPTURE;
9828
9829       if( board[toY][toX] != EmptySquare ) {
9830            board[EP_STATUS] = EP_CAPTURE;
9831            if( (fromX != toX || fromY != toY) && // not igui!
9832                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9833                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9834                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9835            }
9836       }
9837
9838       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9839            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9840                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9841       } else
9842       if( board[fromY][fromX] == WhitePawn ) {
9843            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9844                board[EP_STATUS] = EP_PAWN_MOVE;
9845            if( toY-fromY==2) {
9846                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9847                         gameInfo.variant != VariantBerolina || toX < fromX)
9848                       board[EP_STATUS] = toX | berolina;
9849                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9850                         gameInfo.variant != VariantBerolina || toX > fromX)
9851                       board[EP_STATUS] = toX;
9852            }
9853       } else
9854       if( board[fromY][fromX] == BlackPawn ) {
9855            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9856                board[EP_STATUS] = EP_PAWN_MOVE;
9857            if( toY-fromY== -2) {
9858                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9859                         gameInfo.variant != VariantBerolina || toX < fromX)
9860                       board[EP_STATUS] = toX | berolina;
9861                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9862                         gameInfo.variant != VariantBerolina || toX > fromX)
9863                       board[EP_STATUS] = toX;
9864            }
9865        }
9866
9867        for(i=0; i<nrCastlingRights; i++) {
9868            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9869               board[CASTLING][i] == toX   && castlingRank[i] == toY
9870              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9871        }
9872
9873        if(gameInfo.variant == VariantSChess) { // update virginity
9874            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9875            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9876            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9877            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9878        }
9879
9880      if (fromX == toX && fromY == toY) return;
9881
9882      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9883      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9884      if(gameInfo.variant == VariantKnightmate)
9885          king += (int) WhiteUnicorn - (int) WhiteKing;
9886
9887     /* Code added by Tord: */
9888     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9889     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9890         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9891       board[fromY][fromX] = EmptySquare;
9892       board[toY][toX] = EmptySquare;
9893       if((toX > fromX) != (piece == WhiteRook)) {
9894         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9895       } else {
9896         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9897       }
9898     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9899                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9900       board[fromY][fromX] = EmptySquare;
9901       board[toY][toX] = EmptySquare;
9902       if((toX > fromX) != (piece == BlackRook)) {
9903         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9904       } else {
9905         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9906       }
9907     /* End of code added by Tord */
9908
9909     } else if (board[fromY][fromX] == king
9910         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9911         && toY == fromY && toX > fromX+1) {
9912         board[fromY][fromX] = EmptySquare;
9913         board[toY][toX] = king;
9914         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9915         board[fromY][BOARD_RGHT-1] = EmptySquare;
9916     } else if (board[fromY][fromX] == king
9917         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9918                && toY == fromY && toX < fromX-1) {
9919         board[fromY][fromX] = EmptySquare;
9920         board[toY][toX] = king;
9921         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9922         board[fromY][BOARD_LEFT] = EmptySquare;
9923     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9924                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9925                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9926                ) {
9927         /* white pawn promotion */
9928         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9929         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9930             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9931         board[fromY][fromX] = EmptySquare;
9932     } else if ((fromY >= BOARD_HEIGHT>>1)
9933                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9934                && (toX != fromX)
9935                && gameInfo.variant != VariantXiangqi
9936                && gameInfo.variant != VariantBerolina
9937                && (board[fromY][fromX] == WhitePawn)
9938                && (board[toY][toX] == EmptySquare)) {
9939         board[fromY][fromX] = EmptySquare;
9940         board[toY][toX] = WhitePawn;
9941         captured = board[toY - 1][toX];
9942         board[toY - 1][toX] = EmptySquare;
9943     } else if ((fromY == BOARD_HEIGHT-4)
9944                && (toX == fromX)
9945                && gameInfo.variant == VariantBerolina
9946                && (board[fromY][fromX] == WhitePawn)
9947                && (board[toY][toX] == EmptySquare)) {
9948         board[fromY][fromX] = EmptySquare;
9949         board[toY][toX] = WhitePawn;
9950         if(oldEP & EP_BEROLIN_A) {
9951                 captured = board[fromY][fromX-1];
9952                 board[fromY][fromX-1] = EmptySquare;
9953         }else{  captured = board[fromY][fromX+1];
9954                 board[fromY][fromX+1] = EmptySquare;
9955         }
9956     } else if (board[fromY][fromX] == king
9957         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9958                && toY == fromY && toX > fromX+1) {
9959         board[fromY][fromX] = EmptySquare;
9960         board[toY][toX] = king;
9961         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9962         board[fromY][BOARD_RGHT-1] = EmptySquare;
9963     } else if (board[fromY][fromX] == king
9964         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9965                && toY == fromY && toX < fromX-1) {
9966         board[fromY][fromX] = EmptySquare;
9967         board[toY][toX] = king;
9968         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9969         board[fromY][BOARD_LEFT] = EmptySquare;
9970     } else if (fromY == 7 && fromX == 3
9971                && board[fromY][fromX] == BlackKing
9972                && toY == 7 && toX == 5) {
9973         board[fromY][fromX] = EmptySquare;
9974         board[toY][toX] = BlackKing;
9975         board[fromY][7] = EmptySquare;
9976         board[toY][4] = BlackRook;
9977     } else if (fromY == 7 && fromX == 3
9978                && board[fromY][fromX] == BlackKing
9979                && toY == 7 && toX == 1) {
9980         board[fromY][fromX] = EmptySquare;
9981         board[toY][toX] = BlackKing;
9982         board[fromY][0] = EmptySquare;
9983         board[toY][2] = BlackRook;
9984     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9985                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
9986                && toY < promoRank && promoChar
9987                ) {
9988         /* black pawn promotion */
9989         board[toY][toX] = CharToPiece(ToLower(promoChar));
9990         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
9991             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9992         board[fromY][fromX] = EmptySquare;
9993     } else if ((fromY < BOARD_HEIGHT>>1)
9994                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality)
9995                && (toX != fromX)
9996                && gameInfo.variant != VariantXiangqi
9997                && gameInfo.variant != VariantBerolina
9998                && (board[fromY][fromX] == BlackPawn)
9999                && (board[toY][toX] == EmptySquare)) {
10000         board[fromY][fromX] = EmptySquare;
10001         board[toY][toX] = BlackPawn;
10002         captured = board[toY + 1][toX];
10003         board[toY + 1][toX] = EmptySquare;
10004     } else if ((fromY == 3)
10005                && (toX == fromX)
10006                && gameInfo.variant == VariantBerolina
10007                && (board[fromY][fromX] == BlackPawn)
10008                && (board[toY][toX] == EmptySquare)) {
10009         board[fromY][fromX] = EmptySquare;
10010         board[toY][toX] = BlackPawn;
10011         if(oldEP & EP_BEROLIN_A) {
10012                 captured = board[fromY][fromX-1];
10013                 board[fromY][fromX-1] = EmptySquare;
10014         }else{  captured = board[fromY][fromX+1];
10015                 board[fromY][fromX+1] = EmptySquare;
10016         }
10017     } else {
10018         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10019         board[fromY][fromX] = EmptySquare;
10020         board[toY][toX] = piece;
10021     }
10022   }
10023
10024     if (gameInfo.holdingsWidth != 0) {
10025
10026       /* !!A lot more code needs to be written to support holdings  */
10027       /* [HGM] OK, so I have written it. Holdings are stored in the */
10028       /* penultimate board files, so they are automaticlly stored   */
10029       /* in the game history.                                       */
10030       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10031                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10032         /* Delete from holdings, by decreasing count */
10033         /* and erasing image if necessary            */
10034         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10035         if(p < (int) BlackPawn) { /* white drop */
10036              p -= (int)WhitePawn;
10037                  p = PieceToNumber((ChessSquare)p);
10038              if(p >= gameInfo.holdingsSize) p = 0;
10039              if(--board[p][BOARD_WIDTH-2] <= 0)
10040                   board[p][BOARD_WIDTH-1] = EmptySquare;
10041              if((int)board[p][BOARD_WIDTH-2] < 0)
10042                         board[p][BOARD_WIDTH-2] = 0;
10043         } else {                  /* black drop */
10044              p -= (int)BlackPawn;
10045                  p = PieceToNumber((ChessSquare)p);
10046              if(p >= gameInfo.holdingsSize) p = 0;
10047              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10048                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10049              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10050                         board[BOARD_HEIGHT-1-p][1] = 0;
10051         }
10052       }
10053       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10054           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10055         /* [HGM] holdings: Add to holdings, if holdings exist */
10056         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10057                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10058                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10059         }
10060         p = (int) captured;
10061         if (p >= (int) BlackPawn) {
10062           p -= (int)BlackPawn;
10063           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10064                   /* in Shogi restore piece to its original  first */
10065                   captured = (ChessSquare) (DEMOTED captured);
10066                   p = DEMOTED p;
10067           }
10068           p = PieceToNumber((ChessSquare)p);
10069           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10070           board[p][BOARD_WIDTH-2]++;
10071           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10072         } else {
10073           p -= (int)WhitePawn;
10074           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
10075                   captured = (ChessSquare) (DEMOTED captured);
10076                   p = DEMOTED p;
10077           }
10078           p = PieceToNumber((ChessSquare)p);
10079           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10080           board[BOARD_HEIGHT-1-p][1]++;
10081           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10082         }
10083       }
10084     } else if (gameInfo.variant == VariantAtomic) {
10085       if (captured != EmptySquare) {
10086         int y, x;
10087         for (y = toY-1; y <= toY+1; y++) {
10088           for (x = toX-1; x <= toX+1; x++) {
10089             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10090                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10091               board[y][x] = EmptySquare;
10092             }
10093           }
10094         }
10095         board[toY][toX] = EmptySquare;
10096       }
10097     }
10098
10099     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10100         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10101     } else
10102     if(promoChar == '+') {
10103         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10104         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10105         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10106           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10107     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10108         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10109         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10110            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10111         board[toY][toX] = newPiece;
10112     }
10113     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10114                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10115         // [HGM] superchess: take promotion piece out of holdings
10116         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10117         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10118             if(!--board[k][BOARD_WIDTH-2])
10119                 board[k][BOARD_WIDTH-1] = EmptySquare;
10120         } else {
10121             if(!--board[BOARD_HEIGHT-1-k][1])
10122                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10123         }
10124     }
10125 }
10126
10127 /* Updates forwardMostMove */
10128 void
10129 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10130 {
10131     int x = toX, y = toY;
10132     char *s = parseList[forwardMostMove];
10133     ChessSquare p = boards[forwardMostMove][toY][toX];
10134 //    forwardMostMove++; // [HGM] bare: moved downstream
10135
10136     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10137     (void) CoordsToAlgebraic(boards[forwardMostMove],
10138                              PosFlags(forwardMostMove),
10139                              fromY, fromX, y, x, promoChar,
10140                              s);
10141     if(killX >= 0 && killY >= 0)
10142         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10143
10144     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10145         int timeLeft; static int lastLoadFlag=0; int king, piece;
10146         piece = boards[forwardMostMove][fromY][fromX];
10147         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10148         if(gameInfo.variant == VariantKnightmate)
10149             king += (int) WhiteUnicorn - (int) WhiteKing;
10150         if(forwardMostMove == 0) {
10151             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10152                 fprintf(serverMoves, "%s;", UserName());
10153             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10154                 fprintf(serverMoves, "%s;", second.tidy);
10155             fprintf(serverMoves, "%s;", first.tidy);
10156             if(gameMode == MachinePlaysWhite)
10157                 fprintf(serverMoves, "%s;", UserName());
10158             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10159                 fprintf(serverMoves, "%s;", second.tidy);
10160         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10161         lastLoadFlag = loadFlag;
10162         // print base move
10163         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10164         // print castling suffix
10165         if( toY == fromY && piece == king ) {
10166             if(toX-fromX > 1)
10167                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10168             if(fromX-toX >1)
10169                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10170         }
10171         // e.p. suffix
10172         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10173              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10174              boards[forwardMostMove][toY][toX] == EmptySquare
10175              && fromX != toX && fromY != toY)
10176                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10177         // promotion suffix
10178         if(promoChar != NULLCHAR) {
10179             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10180                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10181                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10182             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10183         }
10184         if(!loadFlag) {
10185                 char buf[MOVE_LEN*2], *p; int len;
10186             fprintf(serverMoves, "/%d/%d",
10187                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10188             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10189             else                      timeLeft = blackTimeRemaining/1000;
10190             fprintf(serverMoves, "/%d", timeLeft);
10191                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10192                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10193                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10194                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10195             fprintf(serverMoves, "/%s", buf);
10196         }
10197         fflush(serverMoves);
10198     }
10199
10200     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10201         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10202       return;
10203     }
10204     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10205     if (commentList[forwardMostMove+1] != NULL) {
10206         free(commentList[forwardMostMove+1]);
10207         commentList[forwardMostMove+1] = NULL;
10208     }
10209     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10210     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10211     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10212     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10213     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10214     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10215     adjustedClock = FALSE;
10216     gameInfo.result = GameUnfinished;
10217     if (gameInfo.resultDetails != NULL) {
10218         free(gameInfo.resultDetails);
10219         gameInfo.resultDetails = NULL;
10220     }
10221     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10222                               moveList[forwardMostMove - 1]);
10223     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10224       case MT_NONE:
10225       case MT_STALEMATE:
10226       default:
10227         break;
10228       case MT_CHECK:
10229         if(!IS_SHOGI(gameInfo.variant))
10230             strcat(parseList[forwardMostMove - 1], "+");
10231         break;
10232       case MT_CHECKMATE:
10233       case MT_STAINMATE:
10234         strcat(parseList[forwardMostMove - 1], "#");
10235         break;
10236     }
10237 }
10238
10239 /* Updates currentMove if not pausing */
10240 void
10241 ShowMove (int fromX, int fromY, int toX, int toY)
10242 {
10243     int instant = (gameMode == PlayFromGameFile) ?
10244         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10245     if(appData.noGUI) return;
10246     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10247         if (!instant) {
10248             if (forwardMostMove == currentMove + 1) {
10249                 AnimateMove(boards[forwardMostMove - 1],
10250                             fromX, fromY, toX, toY);
10251             }
10252         }
10253         currentMove = forwardMostMove;
10254     }
10255
10256     killX = killY = -1; // [HGM] lion: used up
10257
10258     if (instant) return;
10259
10260     DisplayMove(currentMove - 1);
10261     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10262             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10263                 SetHighlights(fromX, fromY, toX, toY);
10264             }
10265     }
10266     DrawPosition(FALSE, boards[currentMove]);
10267     DisplayBothClocks();
10268     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10269 }
10270
10271 void
10272 SendEgtPath (ChessProgramState *cps)
10273 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10274         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10275
10276         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10277
10278         while(*p) {
10279             char c, *q = name+1, *r, *s;
10280
10281             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10282             while(*p && *p != ',') *q++ = *p++;
10283             *q++ = ':'; *q = 0;
10284             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10285                 strcmp(name, ",nalimov:") == 0 ) {
10286                 // take nalimov path from the menu-changeable option first, if it is defined
10287               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10288                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10289             } else
10290             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10291                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10292                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10293                 s = r = StrStr(s, ":") + 1; // beginning of path info
10294                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10295                 c = *r; *r = 0;             // temporarily null-terminate path info
10296                     *--q = 0;               // strip of trailig ':' from name
10297                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10298                 *r = c;
10299                 SendToProgram(buf,cps);     // send egtbpath command for this format
10300             }
10301             if(*p == ',') p++; // read away comma to position for next format name
10302         }
10303 }
10304
10305 static int
10306 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10307 {
10308       int width = 8, height = 8, holdings = 0;             // most common sizes
10309       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10310       // correct the deviations default for each variant
10311       if( v == VariantXiangqi ) width = 9,  height = 10;
10312       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10313       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10314       if( v == VariantCapablanca || v == VariantCapaRandom ||
10315           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10316                                 width = 10;
10317       if( v == VariantCourier ) width = 12;
10318       if( v == VariantSuper )                            holdings = 8;
10319       if( v == VariantGreat )   width = 10,              holdings = 8;
10320       if( v == VariantSChess )                           holdings = 7;
10321       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10322       if( v == VariantChuChess) width = 10, height = 10;
10323       if( v == VariantChu )     width = 12, height = 12;
10324       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10325              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10326              holdingsSize >= 0 && holdingsSize != holdings;
10327 }
10328
10329 char variantError[MSG_SIZ];
10330
10331 char *
10332 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10333 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10334       char *p, *variant = VariantName(v);
10335       static char b[MSG_SIZ];
10336       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10337            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10338                                                holdingsSize, variant); // cook up sized variant name
10339            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10340            if(StrStr(list, b) == NULL) {
10341                // specific sized variant not known, check if general sizing allowed
10342                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10343                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10344                             boardWidth, boardHeight, holdingsSize, engine);
10345                    return NULL;
10346                }
10347                /* [HGM] here we really should compare with the maximum supported board size */
10348            }
10349       } else snprintf(b, MSG_SIZ,"%s", variant);
10350       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10351       p = StrStr(list, b);
10352       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10353       if(p == NULL) {
10354           // occurs not at all in list, or only as sub-string
10355           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10356           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10357               int l = strlen(variantError);
10358               char *q;
10359               while(p != list && p[-1] != ',') p--;
10360               q = strchr(p, ',');
10361               if(q) *q = NULLCHAR;
10362               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10363               if(q) *q= ',';
10364           }
10365           return NULL;
10366       }
10367       return b;
10368 }
10369
10370 void
10371 InitChessProgram (ChessProgramState *cps, int setup)
10372 /* setup needed to setup FRC opening position */
10373 {
10374     char buf[MSG_SIZ], *b;
10375     if (appData.noChessProgram) return;
10376     hintRequested = FALSE;
10377     bookRequested = FALSE;
10378
10379     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10380     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10381     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10382     if(cps->memSize) { /* [HGM] memory */
10383       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10384         SendToProgram(buf, cps);
10385     }
10386     SendEgtPath(cps); /* [HGM] EGT */
10387     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10388       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10389         SendToProgram(buf, cps);
10390     }
10391
10392     SendToProgram(cps->initString, cps);
10393     if (gameInfo.variant != VariantNormal &&
10394         gameInfo.variant != VariantLoadable
10395         /* [HGM] also send variant if board size non-standard */
10396         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10397
10398       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10399                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10400       if (b == NULL) {
10401         DisplayFatalError(variantError, 0, 1);
10402         return;
10403       }
10404
10405       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10406       SendToProgram(buf, cps);
10407     }
10408     currentlyInitializedVariant = gameInfo.variant;
10409
10410     /* [HGM] send opening position in FRC to first engine */
10411     if(setup) {
10412           SendToProgram("force\n", cps);
10413           SendBoard(cps, 0);
10414           /* engine is now in force mode! Set flag to wake it up after first move. */
10415           setboardSpoiledMachineBlack = 1;
10416     }
10417
10418     if (cps->sendICS) {
10419       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10420       SendToProgram(buf, cps);
10421     }
10422     cps->maybeThinking = FALSE;
10423     cps->offeredDraw = 0;
10424     if (!appData.icsActive) {
10425         SendTimeControl(cps, movesPerSession, timeControl,
10426                         timeIncrement, appData.searchDepth,
10427                         searchTime);
10428     }
10429     if (appData.showThinking
10430         // [HGM] thinking: four options require thinking output to be sent
10431         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10432                                 ) {
10433         SendToProgram("post\n", cps);
10434     }
10435     SendToProgram("hard\n", cps);
10436     if (!appData.ponderNextMove) {
10437         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10438            it without being sure what state we are in first.  "hard"
10439            is not a toggle, so that one is OK.
10440          */
10441         SendToProgram("easy\n", cps);
10442     }
10443     if (cps->usePing) {
10444       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10445       SendToProgram(buf, cps);
10446     }
10447     cps->initDone = TRUE;
10448     ClearEngineOutputPane(cps == &second);
10449 }
10450
10451
10452 void
10453 ResendOptions (ChessProgramState *cps)
10454 { // send the stored value of the options
10455   int i;
10456   char buf[MSG_SIZ];
10457   Option *opt = cps->option;
10458   for(i=0; i<cps->nrOptions; i++, opt++) {
10459       switch(opt->type) {
10460         case Spin:
10461         case Slider:
10462         case CheckBox:
10463             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10464           break;
10465         case ComboBox:
10466           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10467           break;
10468         default:
10469             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10470           break;
10471         case Button:
10472         case SaveButton:
10473           continue;
10474       }
10475       SendToProgram(buf, cps);
10476   }
10477 }
10478
10479 void
10480 StartChessProgram (ChessProgramState *cps)
10481 {
10482     char buf[MSG_SIZ];
10483     int err;
10484
10485     if (appData.noChessProgram) return;
10486     cps->initDone = FALSE;
10487
10488     if (strcmp(cps->host, "localhost") == 0) {
10489         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10490     } else if (*appData.remoteShell == NULLCHAR) {
10491         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10492     } else {
10493         if (*appData.remoteUser == NULLCHAR) {
10494           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10495                     cps->program);
10496         } else {
10497           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10498                     cps->host, appData.remoteUser, cps->program);
10499         }
10500         err = StartChildProcess(buf, "", &cps->pr);
10501     }
10502
10503     if (err != 0) {
10504       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10505         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10506         if(cps != &first) return;
10507         appData.noChessProgram = TRUE;
10508         ThawUI();
10509         SetNCPMode();
10510 //      DisplayFatalError(buf, err, 1);
10511 //      cps->pr = NoProc;
10512 //      cps->isr = NULL;
10513         return;
10514     }
10515
10516     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10517     if (cps->protocolVersion > 1) {
10518       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10519       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10520         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10521         cps->comboCnt = 0;  //                and values of combo boxes
10522       }
10523       SendToProgram(buf, cps);
10524       if(cps->reload) ResendOptions(cps);
10525     } else {
10526       SendToProgram("xboard\n", cps);
10527     }
10528 }
10529
10530 void
10531 TwoMachinesEventIfReady P((void))
10532 {
10533   static int curMess = 0;
10534   if (first.lastPing != first.lastPong) {
10535     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10536     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10537     return;
10538   }
10539   if (second.lastPing != second.lastPong) {
10540     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10541     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10542     return;
10543   }
10544   DisplayMessage("", ""); curMess = 0;
10545   TwoMachinesEvent();
10546 }
10547
10548 char *
10549 MakeName (char *template)
10550 {
10551     time_t clock;
10552     struct tm *tm;
10553     static char buf[MSG_SIZ];
10554     char *p = buf;
10555     int i;
10556
10557     clock = time((time_t *)NULL);
10558     tm = localtime(&clock);
10559
10560     while(*p++ = *template++) if(p[-1] == '%') {
10561         switch(*template++) {
10562           case 0:   *p = 0; return buf;
10563           case 'Y': i = tm->tm_year+1900; break;
10564           case 'y': i = tm->tm_year-100; break;
10565           case 'M': i = tm->tm_mon+1; break;
10566           case 'd': i = tm->tm_mday; break;
10567           case 'h': i = tm->tm_hour; break;
10568           case 'm': i = tm->tm_min; break;
10569           case 's': i = tm->tm_sec; break;
10570           default:  i = 0;
10571         }
10572         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10573     }
10574     return buf;
10575 }
10576
10577 int
10578 CountPlayers (char *p)
10579 {
10580     int n = 0;
10581     while(p = strchr(p, '\n')) p++, n++; // count participants
10582     return n;
10583 }
10584
10585 FILE *
10586 WriteTourneyFile (char *results, FILE *f)
10587 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10588     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10589     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10590         // create a file with tournament description
10591         fprintf(f, "-participants {%s}\n", appData.participants);
10592         fprintf(f, "-seedBase %d\n", appData.seedBase);
10593         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10594         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10595         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10596         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10597         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10598         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10599         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10600         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10601         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10602         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10603         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10604         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10605         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10606         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10607         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10608         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10609         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10610         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10611         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10612         fprintf(f, "-smpCores %d\n", appData.smpCores);
10613         if(searchTime > 0)
10614                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10615         else {
10616                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10617                 fprintf(f, "-tc %s\n", appData.timeControl);
10618                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10619         }
10620         fprintf(f, "-results \"%s\"\n", results);
10621     }
10622     return f;
10623 }
10624
10625 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10626
10627 void
10628 Substitute (char *participants, int expunge)
10629 {
10630     int i, changed, changes=0, nPlayers=0;
10631     char *p, *q, *r, buf[MSG_SIZ];
10632     if(participants == NULL) return;
10633     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10634     r = p = participants; q = appData.participants;
10635     while(*p && *p == *q) {
10636         if(*p == '\n') r = p+1, nPlayers++;
10637         p++; q++;
10638     }
10639     if(*p) { // difference
10640         while(*p && *p++ != '\n');
10641         while(*q && *q++ != '\n');
10642       changed = nPlayers;
10643         changes = 1 + (strcmp(p, q) != 0);
10644     }
10645     if(changes == 1) { // a single engine mnemonic was changed
10646         q = r; while(*q) nPlayers += (*q++ == '\n');
10647         p = buf; while(*r && (*p = *r++) != '\n') p++;
10648         *p = NULLCHAR;
10649         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10650         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10651         if(mnemonic[i]) { // The substitute is valid
10652             FILE *f;
10653             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10654                 flock(fileno(f), LOCK_EX);
10655                 ParseArgsFromFile(f);
10656                 fseek(f, 0, SEEK_SET);
10657                 FREE(appData.participants); appData.participants = participants;
10658                 if(expunge) { // erase results of replaced engine
10659                     int len = strlen(appData.results), w, b, dummy;
10660                     for(i=0; i<len; i++) {
10661                         Pairing(i, nPlayers, &w, &b, &dummy);
10662                         if((w == changed || b == changed) && appData.results[i] == '*') {
10663                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10664                             fclose(f);
10665                             return;
10666                         }
10667                     }
10668                     for(i=0; i<len; i++) {
10669                         Pairing(i, nPlayers, &w, &b, &dummy);
10670                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10671                     }
10672                 }
10673                 WriteTourneyFile(appData.results, f);
10674                 fclose(f); // release lock
10675                 return;
10676             }
10677         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10678     }
10679     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10680     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10681     free(participants);
10682     return;
10683 }
10684
10685 int
10686 CheckPlayers (char *participants)
10687 {
10688         int i;
10689         char buf[MSG_SIZ], *p;
10690         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10691         while(p = strchr(participants, '\n')) {
10692             *p = NULLCHAR;
10693             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10694             if(!mnemonic[i]) {
10695                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10696                 *p = '\n';
10697                 DisplayError(buf, 0);
10698                 return 1;
10699             }
10700             *p = '\n';
10701             participants = p + 1;
10702         }
10703         return 0;
10704 }
10705
10706 int
10707 CreateTourney (char *name)
10708 {
10709         FILE *f;
10710         if(matchMode && strcmp(name, appData.tourneyFile)) {
10711              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10712         }
10713         if(name[0] == NULLCHAR) {
10714             if(appData.participants[0])
10715                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10716             return 0;
10717         }
10718         f = fopen(name, "r");
10719         if(f) { // file exists
10720             ASSIGN(appData.tourneyFile, name);
10721             ParseArgsFromFile(f); // parse it
10722         } else {
10723             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10724             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10725                 DisplayError(_("Not enough participants"), 0);
10726                 return 0;
10727             }
10728             if(CheckPlayers(appData.participants)) return 0;
10729             ASSIGN(appData.tourneyFile, name);
10730             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10731             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10732         }
10733         fclose(f);
10734         appData.noChessProgram = FALSE;
10735         appData.clockMode = TRUE;
10736         SetGNUMode();
10737         return 1;
10738 }
10739
10740 int
10741 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10742 {
10743     char buf[MSG_SIZ], *p, *q;
10744     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10745     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10746     skip = !all && group[0]; // if group requested, we start in skip mode
10747     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10748         p = names; q = buf; header = 0;
10749         while(*p && *p != '\n') *q++ = *p++;
10750         *q = 0;
10751         if(*p == '\n') p++;
10752         if(buf[0] == '#') {
10753             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10754             depth++; // we must be entering a new group
10755             if(all) continue; // suppress printing group headers when complete list requested
10756             header = 1;
10757             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10758         }
10759         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10760         if(engineList[i]) free(engineList[i]);
10761         engineList[i] = strdup(buf);
10762         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10763         if(engineMnemonic[i]) free(engineMnemonic[i]);
10764         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10765             strcat(buf, " (");
10766             sscanf(q + 8, "%s", buf + strlen(buf));
10767             strcat(buf, ")");
10768         }
10769         engineMnemonic[i] = strdup(buf);
10770         i++;
10771     }
10772     engineList[i] = engineMnemonic[i] = NULL;
10773     return i;
10774 }
10775
10776 // following implemented as macro to avoid type limitations
10777 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10778
10779 void
10780 SwapEngines (int n)
10781 {   // swap settings for first engine and other engine (so far only some selected options)
10782     int h;
10783     char *p;
10784     if(n == 0) return;
10785     SWAP(directory, p)
10786     SWAP(chessProgram, p)
10787     SWAP(isUCI, h)
10788     SWAP(hasOwnBookUCI, h)
10789     SWAP(protocolVersion, h)
10790     SWAP(reuse, h)
10791     SWAP(scoreIsAbsolute, h)
10792     SWAP(timeOdds, h)
10793     SWAP(logo, p)
10794     SWAP(pgnName, p)
10795     SWAP(pvSAN, h)
10796     SWAP(engOptions, p)
10797     SWAP(engInitString, p)
10798     SWAP(computerString, p)
10799     SWAP(features, p)
10800     SWAP(fenOverride, p)
10801     SWAP(NPS, h)
10802     SWAP(accumulateTC, h)
10803     SWAP(drawDepth, h)
10804     SWAP(host, p)
10805 }
10806
10807 int
10808 GetEngineLine (char *s, int n)
10809 {
10810     int i;
10811     char buf[MSG_SIZ];
10812     extern char *icsNames;
10813     if(!s || !*s) return 0;
10814     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10815     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10816     if(!mnemonic[i]) return 0;
10817     if(n == 11) return 1; // just testing if there was a match
10818     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10819     if(n == 1) SwapEngines(n);
10820     ParseArgsFromString(buf);
10821     if(n == 1) SwapEngines(n);
10822     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10823         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10824         ParseArgsFromString(buf);
10825     }
10826     return 1;
10827 }
10828
10829 int
10830 SetPlayer (int player, char *p)
10831 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10832     int i;
10833     char buf[MSG_SIZ], *engineName;
10834     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10835     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10836     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10837     if(mnemonic[i]) {
10838         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10839         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10840         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10841         ParseArgsFromString(buf);
10842     } else { // no engine with this nickname is installed!
10843         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10844         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10845         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10846         ModeHighlight();
10847         DisplayError(buf, 0);
10848         return 0;
10849     }
10850     free(engineName);
10851     return i;
10852 }
10853
10854 char *recentEngines;
10855
10856 void
10857 RecentEngineEvent (int nr)
10858 {
10859     int n;
10860 //    SwapEngines(1); // bump first to second
10861 //    ReplaceEngine(&second, 1); // and load it there
10862     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10863     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10864     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10865         ReplaceEngine(&first, 0);
10866         FloatToFront(&appData.recentEngineList, command[n]);
10867     }
10868 }
10869
10870 int
10871 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10872 {   // determine players from game number
10873     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10874
10875     if(appData.tourneyType == 0) {
10876         roundsPerCycle = (nPlayers - 1) | 1;
10877         pairingsPerRound = nPlayers / 2;
10878     } else if(appData.tourneyType > 0) {
10879         roundsPerCycle = nPlayers - appData.tourneyType;
10880         pairingsPerRound = appData.tourneyType;
10881     }
10882     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10883     gamesPerCycle = gamesPerRound * roundsPerCycle;
10884     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10885     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10886     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10887     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10888     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10889     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10890
10891     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10892     if(appData.roundSync) *syncInterval = gamesPerRound;
10893
10894     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10895
10896     if(appData.tourneyType == 0) {
10897         if(curPairing == (nPlayers-1)/2 ) {
10898             *whitePlayer = curRound;
10899             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10900         } else {
10901             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10902             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10903             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10904             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10905         }
10906     } else if(appData.tourneyType > 1) {
10907         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10908         *whitePlayer = curRound + appData.tourneyType;
10909     } else if(appData.tourneyType > 0) {
10910         *whitePlayer = curPairing;
10911         *blackPlayer = curRound + appData.tourneyType;
10912     }
10913
10914     // take care of white/black alternation per round.
10915     // For cycles and games this is already taken care of by default, derived from matchGame!
10916     return curRound & 1;
10917 }
10918
10919 int
10920 NextTourneyGame (int nr, int *swapColors)
10921 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10922     char *p, *q;
10923     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
10924     FILE *tf;
10925     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10926     tf = fopen(appData.tourneyFile, "r");
10927     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10928     ParseArgsFromFile(tf); fclose(tf);
10929     InitTimeControls(); // TC might be altered from tourney file
10930
10931     nPlayers = CountPlayers(appData.participants); // count participants
10932     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10933     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10934
10935     if(syncInterval) {
10936         p = q = appData.results;
10937         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10938         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10939             DisplayMessage(_("Waiting for other game(s)"),"");
10940             waitingForGame = TRUE;
10941             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10942             return 0;
10943         }
10944         waitingForGame = FALSE;
10945     }
10946
10947     if(appData.tourneyType < 0) {
10948         if(nr>=0 && !pairingReceived) {
10949             char buf[1<<16];
10950             if(pairing.pr == NoProc) {
10951                 if(!appData.pairingEngine[0]) {
10952                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10953                     return 0;
10954                 }
10955                 StartChessProgram(&pairing); // starts the pairing engine
10956             }
10957             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10958             SendToProgram(buf, &pairing);
10959             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10960             SendToProgram(buf, &pairing);
10961             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10962         }
10963         pairingReceived = 0;                              // ... so we continue here
10964         *swapColors = 0;
10965         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10966         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10967         matchGame = 1; roundNr = nr / syncInterval + 1;
10968     }
10969
10970     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10971
10972     // redefine engines, engine dir, etc.
10973     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10974     if(first.pr == NoProc) {
10975       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
10976       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10977     }
10978     if(second.pr == NoProc) {
10979       SwapEngines(1);
10980       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
10981       SwapEngines(1);         // and make that valid for second engine by swapping
10982       InitEngine(&second, 1);
10983     }
10984     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10985     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10986     return OK;
10987 }
10988
10989 void
10990 NextMatchGame ()
10991 {   // performs game initialization that does not invoke engines, and then tries to start the game
10992     int res, firstWhite, swapColors = 0;
10993     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10994     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
10995         char buf[MSG_SIZ];
10996         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10997         if(strcmp(buf, currentDebugFile)) { // name has changed
10998             FILE *f = fopen(buf, "w");
10999             if(f) { // if opening the new file failed, just keep using the old one
11000                 ASSIGN(currentDebugFile, buf);
11001                 fclose(debugFP);
11002                 debugFP = f;
11003             }
11004             if(appData.serverFileName) {
11005                 if(serverFP) fclose(serverFP);
11006                 serverFP = fopen(appData.serverFileName, "w");
11007                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11008                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11009             }
11010         }
11011     }
11012     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11013     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11014     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11015     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11016     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11017     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11018     Reset(FALSE, first.pr != NoProc);
11019     res = LoadGameOrPosition(matchGame); // setup game
11020     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11021     if(!res) return; // abort when bad game/pos file
11022     TwoMachinesEvent();
11023 }
11024
11025 void
11026 UserAdjudicationEvent (int result)
11027 {
11028     ChessMove gameResult = GameIsDrawn;
11029
11030     if( result > 0 ) {
11031         gameResult = WhiteWins;
11032     }
11033     else if( result < 0 ) {
11034         gameResult = BlackWins;
11035     }
11036
11037     if( gameMode == TwoMachinesPlay ) {
11038         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11039     }
11040 }
11041
11042
11043 // [HGM] save: calculate checksum of game to make games easily identifiable
11044 int
11045 StringCheckSum (char *s)
11046 {
11047         int i = 0;
11048         if(s==NULL) return 0;
11049         while(*s) i = i*259 + *s++;
11050         return i;
11051 }
11052
11053 int
11054 GameCheckSum ()
11055 {
11056         int i, sum=0;
11057         for(i=backwardMostMove; i<forwardMostMove; i++) {
11058                 sum += pvInfoList[i].depth;
11059                 sum += StringCheckSum(parseList[i]);
11060                 sum += StringCheckSum(commentList[i]);
11061                 sum *= 261;
11062         }
11063         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11064         return sum + StringCheckSum(commentList[i]);
11065 } // end of save patch
11066
11067 void
11068 GameEnds (ChessMove result, char *resultDetails, int whosays)
11069 {
11070     GameMode nextGameMode;
11071     int isIcsGame;
11072     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11073
11074     if(endingGame) return; /* [HGM] crash: forbid recursion */
11075     endingGame = 1;
11076     if(twoBoards) { // [HGM] dual: switch back to one board
11077         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11078         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11079     }
11080     if (appData.debugMode) {
11081       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11082               result, resultDetails ? resultDetails : "(null)", whosays);
11083     }
11084
11085     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11086
11087     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11088
11089     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11090         /* If we are playing on ICS, the server decides when the
11091            game is over, but the engine can offer to draw, claim
11092            a draw, or resign.
11093          */
11094 #if ZIPPY
11095         if (appData.zippyPlay && first.initDone) {
11096             if (result == GameIsDrawn) {
11097                 /* In case draw still needs to be claimed */
11098                 SendToICS(ics_prefix);
11099                 SendToICS("draw\n");
11100             } else if (StrCaseStr(resultDetails, "resign")) {
11101                 SendToICS(ics_prefix);
11102                 SendToICS("resign\n");
11103             }
11104         }
11105 #endif
11106         endingGame = 0; /* [HGM] crash */
11107         return;
11108     }
11109
11110     /* If we're loading the game from a file, stop */
11111     if (whosays == GE_FILE) {
11112       (void) StopLoadGameTimer();
11113       gameFileFP = NULL;
11114     }
11115
11116     /* Cancel draw offers */
11117     first.offeredDraw = second.offeredDraw = 0;
11118
11119     /* If this is an ICS game, only ICS can really say it's done;
11120        if not, anyone can. */
11121     isIcsGame = (gameMode == IcsPlayingWhite ||
11122                  gameMode == IcsPlayingBlack ||
11123                  gameMode == IcsObserving    ||
11124                  gameMode == IcsExamining);
11125
11126     if (!isIcsGame || whosays == GE_ICS) {
11127         /* OK -- not an ICS game, or ICS said it was done */
11128         StopClocks();
11129         if (!isIcsGame && !appData.noChessProgram)
11130           SetUserThinkingEnables();
11131
11132         /* [HGM] if a machine claims the game end we verify this claim */
11133         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11134             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11135                 char claimer;
11136                 ChessMove trueResult = (ChessMove) -1;
11137
11138                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11139                                             first.twoMachinesColor[0] :
11140                                             second.twoMachinesColor[0] ;
11141
11142                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11143                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11144                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11145                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11146                 } else
11147                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11148                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11149                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11150                 } else
11151                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11152                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11153                 }
11154
11155                 // now verify win claims, but not in drop games, as we don't understand those yet
11156                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11157                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11158                     (result == WhiteWins && claimer == 'w' ||
11159                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11160                       if (appData.debugMode) {
11161                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11162                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11163                       }
11164                       if(result != trueResult) {
11165                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11166                               result = claimer == 'w' ? BlackWins : WhiteWins;
11167                               resultDetails = buf;
11168                       }
11169                 } else
11170                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11171                     && (forwardMostMove <= backwardMostMove ||
11172                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11173                         (claimer=='b')==(forwardMostMove&1))
11174                                                                                   ) {
11175                       /* [HGM] verify: draws that were not flagged are false claims */
11176                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11177                       result = claimer == 'w' ? BlackWins : WhiteWins;
11178                       resultDetails = buf;
11179                 }
11180                 /* (Claiming a loss is accepted no questions asked!) */
11181             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11182                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11183                 result = GameUnfinished;
11184                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11185             }
11186             /* [HGM] bare: don't allow bare King to win */
11187             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11188                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11189                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11190                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11191                && result != GameIsDrawn)
11192             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11193                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11194                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11195                         if(p >= 0 && p <= (int)WhiteKing) k++;
11196                 }
11197                 if (appData.debugMode) {
11198                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11199                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11200                 }
11201                 if(k <= 1) {
11202                         result = GameIsDrawn;
11203                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11204                         resultDetails = buf;
11205                 }
11206             }
11207         }
11208
11209
11210         if(serverMoves != NULL && !loadFlag) { char c = '=';
11211             if(result==WhiteWins) c = '+';
11212             if(result==BlackWins) c = '-';
11213             if(resultDetails != NULL)
11214                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11215         }
11216         if (resultDetails != NULL) {
11217             gameInfo.result = result;
11218             gameInfo.resultDetails = StrSave(resultDetails);
11219
11220             /* display last move only if game was not loaded from file */
11221             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11222                 DisplayMove(currentMove - 1);
11223
11224             if (forwardMostMove != 0) {
11225                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11226                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11227                                                                 ) {
11228                     if (*appData.saveGameFile != NULLCHAR) {
11229                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11230                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11231                         else
11232                         SaveGameToFile(appData.saveGameFile, TRUE);
11233                     } else if (appData.autoSaveGames) {
11234                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11235                     }
11236                     if (*appData.savePositionFile != NULLCHAR) {
11237                         SavePositionToFile(appData.savePositionFile);
11238                     }
11239                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11240                 }
11241             }
11242
11243             /* Tell program how game ended in case it is learning */
11244             /* [HGM] Moved this to after saving the PGN, just in case */
11245             /* engine died and we got here through time loss. In that */
11246             /* case we will get a fatal error writing the pipe, which */
11247             /* would otherwise lose us the PGN.                       */
11248             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11249             /* output during GameEnds should never be fatal anymore   */
11250             if (gameMode == MachinePlaysWhite ||
11251                 gameMode == MachinePlaysBlack ||
11252                 gameMode == TwoMachinesPlay ||
11253                 gameMode == IcsPlayingWhite ||
11254                 gameMode == IcsPlayingBlack ||
11255                 gameMode == BeginningOfGame) {
11256                 char buf[MSG_SIZ];
11257                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11258                         resultDetails);
11259                 if (first.pr != NoProc) {
11260                     SendToProgram(buf, &first);
11261                 }
11262                 if (second.pr != NoProc &&
11263                     gameMode == TwoMachinesPlay) {
11264                     SendToProgram(buf, &second);
11265                 }
11266             }
11267         }
11268
11269         if (appData.icsActive) {
11270             if (appData.quietPlay &&
11271                 (gameMode == IcsPlayingWhite ||
11272                  gameMode == IcsPlayingBlack)) {
11273                 SendToICS(ics_prefix);
11274                 SendToICS("set shout 1\n");
11275             }
11276             nextGameMode = IcsIdle;
11277             ics_user_moved = FALSE;
11278             /* clean up premove.  It's ugly when the game has ended and the
11279              * premove highlights are still on the board.
11280              */
11281             if (gotPremove) {
11282               gotPremove = FALSE;
11283               ClearPremoveHighlights();
11284               DrawPosition(FALSE, boards[currentMove]);
11285             }
11286             if (whosays == GE_ICS) {
11287                 switch (result) {
11288                 case WhiteWins:
11289                     if (gameMode == IcsPlayingWhite)
11290                         PlayIcsWinSound();
11291                     else if(gameMode == IcsPlayingBlack)
11292                         PlayIcsLossSound();
11293                     break;
11294                 case BlackWins:
11295                     if (gameMode == IcsPlayingBlack)
11296                         PlayIcsWinSound();
11297                     else if(gameMode == IcsPlayingWhite)
11298                         PlayIcsLossSound();
11299                     break;
11300                 case GameIsDrawn:
11301                     PlayIcsDrawSound();
11302                     break;
11303                 default:
11304                     PlayIcsUnfinishedSound();
11305                 }
11306             }
11307             if(appData.quitNext) { ExitEvent(0); return; }
11308         } else if (gameMode == EditGame ||
11309                    gameMode == PlayFromGameFile ||
11310                    gameMode == AnalyzeMode ||
11311                    gameMode == AnalyzeFile) {
11312             nextGameMode = gameMode;
11313         } else {
11314             nextGameMode = EndOfGame;
11315         }
11316         pausing = FALSE;
11317         ModeHighlight();
11318     } else {
11319         nextGameMode = gameMode;
11320     }
11321
11322     if (appData.noChessProgram) {
11323         gameMode = nextGameMode;
11324         ModeHighlight();
11325         endingGame = 0; /* [HGM] crash */
11326         return;
11327     }
11328
11329     if (first.reuse) {
11330         /* Put first chess program into idle state */
11331         if (first.pr != NoProc &&
11332             (gameMode == MachinePlaysWhite ||
11333              gameMode == MachinePlaysBlack ||
11334              gameMode == TwoMachinesPlay ||
11335              gameMode == IcsPlayingWhite ||
11336              gameMode == IcsPlayingBlack ||
11337              gameMode == BeginningOfGame)) {
11338             SendToProgram("force\n", &first);
11339             if (first.usePing) {
11340               char buf[MSG_SIZ];
11341               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11342               SendToProgram(buf, &first);
11343             }
11344         }
11345     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11346         /* Kill off first chess program */
11347         if (first.isr != NULL)
11348           RemoveInputSource(first.isr);
11349         first.isr = NULL;
11350
11351         if (first.pr != NoProc) {
11352             ExitAnalyzeMode();
11353             DoSleep( appData.delayBeforeQuit );
11354             SendToProgram("quit\n", &first);
11355             DoSleep( appData.delayAfterQuit );
11356             DestroyChildProcess(first.pr, first.useSigterm);
11357             first.reload = TRUE;
11358         }
11359         first.pr = NoProc;
11360     }
11361     if (second.reuse) {
11362         /* Put second chess program into idle state */
11363         if (second.pr != NoProc &&
11364             gameMode == TwoMachinesPlay) {
11365             SendToProgram("force\n", &second);
11366             if (second.usePing) {
11367               char buf[MSG_SIZ];
11368               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11369               SendToProgram(buf, &second);
11370             }
11371         }
11372     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11373         /* Kill off second chess program */
11374         if (second.isr != NULL)
11375           RemoveInputSource(second.isr);
11376         second.isr = NULL;
11377
11378         if (second.pr != NoProc) {
11379             DoSleep( appData.delayBeforeQuit );
11380             SendToProgram("quit\n", &second);
11381             DoSleep( appData.delayAfterQuit );
11382             DestroyChildProcess(second.pr, second.useSigterm);
11383             second.reload = TRUE;
11384         }
11385         second.pr = NoProc;
11386     }
11387
11388     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11389         char resChar = '=';
11390         switch (result) {
11391         case WhiteWins:
11392           resChar = '+';
11393           if (first.twoMachinesColor[0] == 'w') {
11394             first.matchWins++;
11395           } else {
11396             second.matchWins++;
11397           }
11398           break;
11399         case BlackWins:
11400           resChar = '-';
11401           if (first.twoMachinesColor[0] == 'b') {
11402             first.matchWins++;
11403           } else {
11404             second.matchWins++;
11405           }
11406           break;
11407         case GameUnfinished:
11408           resChar = ' ';
11409         default:
11410           break;
11411         }
11412
11413         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11414         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11415             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11416             ReserveGame(nextGame, resChar); // sets nextGame
11417             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11418             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11419         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11420
11421         if (nextGame <= appData.matchGames && !abortMatch) {
11422             gameMode = nextGameMode;
11423             matchGame = nextGame; // this will be overruled in tourney mode!
11424             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11425             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11426             endingGame = 0; /* [HGM] crash */
11427             return;
11428         } else {
11429             gameMode = nextGameMode;
11430             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11431                      first.tidy, second.tidy,
11432                      first.matchWins, second.matchWins,
11433                      appData.matchGames - (first.matchWins + second.matchWins));
11434             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11435             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11436             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11437             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11438                 first.twoMachinesColor = "black\n";
11439                 second.twoMachinesColor = "white\n";
11440             } else {
11441                 first.twoMachinesColor = "white\n";
11442                 second.twoMachinesColor = "black\n";
11443             }
11444         }
11445     }
11446     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11447         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11448       ExitAnalyzeMode();
11449     gameMode = nextGameMode;
11450     ModeHighlight();
11451     endingGame = 0;  /* [HGM] crash */
11452     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11453         if(matchMode == TRUE) { // match through command line: exit with or without popup
11454             if(ranking) {
11455                 ToNrEvent(forwardMostMove);
11456                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11457                 else ExitEvent(0);
11458             } else DisplayFatalError(buf, 0, 0);
11459         } else { // match through menu; just stop, with or without popup
11460             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11461             ModeHighlight();
11462             if(ranking){
11463                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11464             } else DisplayNote(buf);
11465       }
11466       if(ranking) free(ranking);
11467     }
11468 }
11469
11470 /* Assumes program was just initialized (initString sent).
11471    Leaves program in force mode. */
11472 void
11473 FeedMovesToProgram (ChessProgramState *cps, int upto)
11474 {
11475     int i;
11476
11477     if (appData.debugMode)
11478       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11479               startedFromSetupPosition ? "position and " : "",
11480               backwardMostMove, upto, cps->which);
11481     if(currentlyInitializedVariant != gameInfo.variant) {
11482       char buf[MSG_SIZ];
11483         // [HGM] variantswitch: make engine aware of new variant
11484         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11485                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11486                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11487         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11488         SendToProgram(buf, cps);
11489         currentlyInitializedVariant = gameInfo.variant;
11490     }
11491     SendToProgram("force\n", cps);
11492     if (startedFromSetupPosition) {
11493         SendBoard(cps, backwardMostMove);
11494     if (appData.debugMode) {
11495         fprintf(debugFP, "feedMoves\n");
11496     }
11497     }
11498     for (i = backwardMostMove; i < upto; i++) {
11499         SendMoveToProgram(i, cps);
11500     }
11501 }
11502
11503
11504 int
11505 ResurrectChessProgram ()
11506 {
11507      /* The chess program may have exited.
11508         If so, restart it and feed it all the moves made so far. */
11509     static int doInit = 0;
11510
11511     if (appData.noChessProgram) return 1;
11512
11513     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11514         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11515         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11516         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11517     } else {
11518         if (first.pr != NoProc) return 1;
11519         StartChessProgram(&first);
11520     }
11521     InitChessProgram(&first, FALSE);
11522     FeedMovesToProgram(&first, currentMove);
11523
11524     if (!first.sendTime) {
11525         /* can't tell gnuchess what its clock should read,
11526            so we bow to its notion. */
11527         ResetClocks();
11528         timeRemaining[0][currentMove] = whiteTimeRemaining;
11529         timeRemaining[1][currentMove] = blackTimeRemaining;
11530     }
11531
11532     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11533                 appData.icsEngineAnalyze) && first.analysisSupport) {
11534       SendToProgram("analyze\n", &first);
11535       first.analyzing = TRUE;
11536     }
11537     return 1;
11538 }
11539
11540 /*
11541  * Button procedures
11542  */
11543 void
11544 Reset (int redraw, int init)
11545 {
11546     int i;
11547
11548     if (appData.debugMode) {
11549         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11550                 redraw, init, gameMode);
11551     }
11552     CleanupTail(); // [HGM] vari: delete any stored variations
11553     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11554     pausing = pauseExamInvalid = FALSE;
11555     startedFromSetupPosition = blackPlaysFirst = FALSE;
11556     firstMove = TRUE;
11557     whiteFlag = blackFlag = FALSE;
11558     userOfferedDraw = FALSE;
11559     hintRequested = bookRequested = FALSE;
11560     first.maybeThinking = FALSE;
11561     second.maybeThinking = FALSE;
11562     first.bookSuspend = FALSE; // [HGM] book
11563     second.bookSuspend = FALSE;
11564     thinkOutput[0] = NULLCHAR;
11565     lastHint[0] = NULLCHAR;
11566     ClearGameInfo(&gameInfo);
11567     gameInfo.variant = StringToVariant(appData.variant);
11568     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11569     ics_user_moved = ics_clock_paused = FALSE;
11570     ics_getting_history = H_FALSE;
11571     ics_gamenum = -1;
11572     white_holding[0] = black_holding[0] = NULLCHAR;
11573     ClearProgramStats();
11574     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11575
11576     ResetFrontEnd();
11577     ClearHighlights();
11578     flipView = appData.flipView;
11579     ClearPremoveHighlights();
11580     gotPremove = FALSE;
11581     alarmSounded = FALSE;
11582     killX = killY = -1; // [HGM] lion
11583
11584     GameEnds(EndOfFile, NULL, GE_PLAYER);
11585     if(appData.serverMovesName != NULL) {
11586         /* [HGM] prepare to make moves file for broadcasting */
11587         clock_t t = clock();
11588         if(serverMoves != NULL) fclose(serverMoves);
11589         serverMoves = fopen(appData.serverMovesName, "r");
11590         if(serverMoves != NULL) {
11591             fclose(serverMoves);
11592             /* delay 15 sec before overwriting, so all clients can see end */
11593             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11594         }
11595         serverMoves = fopen(appData.serverMovesName, "w");
11596     }
11597
11598     ExitAnalyzeMode();
11599     gameMode = BeginningOfGame;
11600     ModeHighlight();
11601     if(appData.icsActive) gameInfo.variant = VariantNormal;
11602     currentMove = forwardMostMove = backwardMostMove = 0;
11603     MarkTargetSquares(1);
11604     InitPosition(redraw);
11605     for (i = 0; i < MAX_MOVES; i++) {
11606         if (commentList[i] != NULL) {
11607             free(commentList[i]);
11608             commentList[i] = NULL;
11609         }
11610     }
11611     ResetClocks();
11612     timeRemaining[0][0] = whiteTimeRemaining;
11613     timeRemaining[1][0] = blackTimeRemaining;
11614
11615     if (first.pr == NoProc) {
11616         StartChessProgram(&first);
11617     }
11618     if (init) {
11619             InitChessProgram(&first, startedFromSetupPosition);
11620     }
11621     DisplayTitle("");
11622     DisplayMessage("", "");
11623     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11624     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11625     ClearMap();        // [HGM] exclude: invalidate map
11626 }
11627
11628 void
11629 AutoPlayGameLoop ()
11630 {
11631     for (;;) {
11632         if (!AutoPlayOneMove())
11633           return;
11634         if (matchMode || appData.timeDelay == 0)
11635           continue;
11636         if (appData.timeDelay < 0)
11637           return;
11638         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11639         break;
11640     }
11641 }
11642
11643 void
11644 AnalyzeNextGame()
11645 {
11646     ReloadGame(1); // next game
11647 }
11648
11649 int
11650 AutoPlayOneMove ()
11651 {
11652     int fromX, fromY, toX, toY;
11653
11654     if (appData.debugMode) {
11655       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11656     }
11657
11658     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11659       return FALSE;
11660
11661     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11662       pvInfoList[currentMove].depth = programStats.depth;
11663       pvInfoList[currentMove].score = programStats.score;
11664       pvInfoList[currentMove].time  = 0;
11665       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11666       else { // append analysis of final position as comment
11667         char buf[MSG_SIZ];
11668         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11669         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11670       }
11671       programStats.depth = 0;
11672     }
11673
11674     if (currentMove >= forwardMostMove) {
11675       if(gameMode == AnalyzeFile) {
11676           if(appData.loadGameIndex == -1) {
11677             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11678           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11679           } else {
11680           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11681         }
11682       }
11683 //      gameMode = EndOfGame;
11684 //      ModeHighlight();
11685
11686       /* [AS] Clear current move marker at the end of a game */
11687       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11688
11689       return FALSE;
11690     }
11691
11692     toX = moveList[currentMove][2] - AAA;
11693     toY = moveList[currentMove][3] - ONE;
11694
11695     if (moveList[currentMove][1] == '@') {
11696         if (appData.highlightLastMove) {
11697             SetHighlights(-1, -1, toX, toY);
11698         }
11699     } else {
11700         fromX = moveList[currentMove][0] - AAA;
11701         fromY = moveList[currentMove][1] - ONE;
11702
11703         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11704
11705         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11706
11707         if (appData.highlightLastMove) {
11708             SetHighlights(fromX, fromY, toX, toY);
11709         }
11710     }
11711     DisplayMove(currentMove);
11712     SendMoveToProgram(currentMove++, &first);
11713     DisplayBothClocks();
11714     DrawPosition(FALSE, boards[currentMove]);
11715     // [HGM] PV info: always display, routine tests if empty
11716     DisplayComment(currentMove - 1, commentList[currentMove]);
11717     return TRUE;
11718 }
11719
11720
11721 int
11722 LoadGameOneMove (ChessMove readAhead)
11723 {
11724     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11725     char promoChar = NULLCHAR;
11726     ChessMove moveType;
11727     char move[MSG_SIZ];
11728     char *p, *q;
11729
11730     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11731         gameMode != AnalyzeMode && gameMode != Training) {
11732         gameFileFP = NULL;
11733         return FALSE;
11734     }
11735
11736     yyboardindex = forwardMostMove;
11737     if (readAhead != EndOfFile) {
11738       moveType = readAhead;
11739     } else {
11740       if (gameFileFP == NULL)
11741           return FALSE;
11742       moveType = (ChessMove) Myylex();
11743     }
11744
11745     done = FALSE;
11746     switch (moveType) {
11747       case Comment:
11748         if (appData.debugMode)
11749           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11750         p = yy_text;
11751
11752         /* append the comment but don't display it */
11753         AppendComment(currentMove, p, FALSE);
11754         return TRUE;
11755
11756       case WhiteCapturesEnPassant:
11757       case BlackCapturesEnPassant:
11758       case WhitePromotion:
11759       case BlackPromotion:
11760       case WhiteNonPromotion:
11761       case BlackNonPromotion:
11762       case NormalMove:
11763       case FirstLeg:
11764       case WhiteKingSideCastle:
11765       case WhiteQueenSideCastle:
11766       case BlackKingSideCastle:
11767       case BlackQueenSideCastle:
11768       case WhiteKingSideCastleWild:
11769       case WhiteQueenSideCastleWild:
11770       case BlackKingSideCastleWild:
11771       case BlackQueenSideCastleWild:
11772       /* PUSH Fabien */
11773       case WhiteHSideCastleFR:
11774       case WhiteASideCastleFR:
11775       case BlackHSideCastleFR:
11776       case BlackASideCastleFR:
11777       /* POP Fabien */
11778         if (appData.debugMode)
11779           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11780         fromX = currentMoveString[0] - AAA;
11781         fromY = currentMoveString[1] - ONE;
11782         toX = currentMoveString[2] - AAA;
11783         toY = currentMoveString[3] - ONE;
11784         promoChar = currentMoveString[4];
11785         if(promoChar == ';') promoChar = NULLCHAR;
11786         break;
11787
11788       case WhiteDrop:
11789       case BlackDrop:
11790         if (appData.debugMode)
11791           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11792         fromX = moveType == WhiteDrop ?
11793           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11794         (int) CharToPiece(ToLower(currentMoveString[0]));
11795         fromY = DROP_RANK;
11796         toX = currentMoveString[2] - AAA;
11797         toY = currentMoveString[3] - ONE;
11798         break;
11799
11800       case WhiteWins:
11801       case BlackWins:
11802       case GameIsDrawn:
11803       case GameUnfinished:
11804         if (appData.debugMode)
11805           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11806         p = strchr(yy_text, '{');
11807         if (p == NULL) p = strchr(yy_text, '(');
11808         if (p == NULL) {
11809             p = yy_text;
11810             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11811         } else {
11812             q = strchr(p, *p == '{' ? '}' : ')');
11813             if (q != NULL) *q = NULLCHAR;
11814             p++;
11815         }
11816         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11817         GameEnds(moveType, p, GE_FILE);
11818         done = TRUE;
11819         if (cmailMsgLoaded) {
11820             ClearHighlights();
11821             flipView = WhiteOnMove(currentMove);
11822             if (moveType == GameUnfinished) flipView = !flipView;
11823             if (appData.debugMode)
11824               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11825         }
11826         break;
11827
11828       case EndOfFile:
11829         if (appData.debugMode)
11830           fprintf(debugFP, "Parser hit end of file\n");
11831         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11832           case MT_NONE:
11833           case MT_CHECK:
11834             break;
11835           case MT_CHECKMATE:
11836           case MT_STAINMATE:
11837             if (WhiteOnMove(currentMove)) {
11838                 GameEnds(BlackWins, "Black mates", GE_FILE);
11839             } else {
11840                 GameEnds(WhiteWins, "White mates", GE_FILE);
11841             }
11842             break;
11843           case MT_STALEMATE:
11844             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11845             break;
11846         }
11847         done = TRUE;
11848         break;
11849
11850       case MoveNumberOne:
11851         if (lastLoadGameStart == GNUChessGame) {
11852             /* GNUChessGames have numbers, but they aren't move numbers */
11853             if (appData.debugMode)
11854               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11855                       yy_text, (int) moveType);
11856             return LoadGameOneMove(EndOfFile); /* tail recursion */
11857         }
11858         /* else fall thru */
11859
11860       case XBoardGame:
11861       case GNUChessGame:
11862       case PGNTag:
11863         /* Reached start of next game in file */
11864         if (appData.debugMode)
11865           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11866         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11867           case MT_NONE:
11868           case MT_CHECK:
11869             break;
11870           case MT_CHECKMATE:
11871           case MT_STAINMATE:
11872             if (WhiteOnMove(currentMove)) {
11873                 GameEnds(BlackWins, "Black mates", GE_FILE);
11874             } else {
11875                 GameEnds(WhiteWins, "White mates", GE_FILE);
11876             }
11877             break;
11878           case MT_STALEMATE:
11879             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11880             break;
11881         }
11882         done = TRUE;
11883         break;
11884
11885       case PositionDiagram:     /* should not happen; ignore */
11886       case ElapsedTime:         /* ignore */
11887       case NAG:                 /* ignore */
11888         if (appData.debugMode)
11889           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11890                   yy_text, (int) moveType);
11891         return LoadGameOneMove(EndOfFile); /* tail recursion */
11892
11893       case IllegalMove:
11894         if (appData.testLegality) {
11895             if (appData.debugMode)
11896               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11897             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11898                     (forwardMostMove / 2) + 1,
11899                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11900             DisplayError(move, 0);
11901             done = TRUE;
11902         } else {
11903             if (appData.debugMode)
11904               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11905                       yy_text, currentMoveString);
11906             fromX = currentMoveString[0] - AAA;
11907             fromY = currentMoveString[1] - ONE;
11908             toX = currentMoveString[2] - AAA;
11909             toY = currentMoveString[3] - ONE;
11910             promoChar = currentMoveString[4];
11911         }
11912         break;
11913
11914       case AmbiguousMove:
11915         if (appData.debugMode)
11916           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11917         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11918                 (forwardMostMove / 2) + 1,
11919                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11920         DisplayError(move, 0);
11921         done = TRUE;
11922         break;
11923
11924       default:
11925       case ImpossibleMove:
11926         if (appData.debugMode)
11927           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11928         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11929                 (forwardMostMove / 2) + 1,
11930                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11931         DisplayError(move, 0);
11932         done = TRUE;
11933         break;
11934     }
11935
11936     if (done) {
11937         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11938             DrawPosition(FALSE, boards[currentMove]);
11939             DisplayBothClocks();
11940             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11941               DisplayComment(currentMove - 1, commentList[currentMove]);
11942         }
11943         (void) StopLoadGameTimer();
11944         gameFileFP = NULL;
11945         cmailOldMove = forwardMostMove;
11946         return FALSE;
11947     } else {
11948         /* currentMoveString is set as a side-effect of yylex */
11949
11950         thinkOutput[0] = NULLCHAR;
11951         MakeMove(fromX, fromY, toX, toY, promoChar);
11952         killX = killY = -1; // [HGM] lion: used up
11953         currentMove = forwardMostMove;
11954         return TRUE;
11955     }
11956 }
11957
11958 /* Load the nth game from the given file */
11959 int
11960 LoadGameFromFile (char *filename, int n, char *title, int useList)
11961 {
11962     FILE *f;
11963     char buf[MSG_SIZ];
11964
11965     if (strcmp(filename, "-") == 0) {
11966         f = stdin;
11967         title = "stdin";
11968     } else {
11969         f = fopen(filename, "rb");
11970         if (f == NULL) {
11971           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11972             DisplayError(buf, errno);
11973             return FALSE;
11974         }
11975     }
11976     if (fseek(f, 0, 0) == -1) {
11977         /* f is not seekable; probably a pipe */
11978         useList = FALSE;
11979     }
11980     if (useList && n == 0) {
11981         int error = GameListBuild(f);
11982         if (error) {
11983             DisplayError(_("Cannot build game list"), error);
11984         } else if (!ListEmpty(&gameList) &&
11985                    ((ListGame *) gameList.tailPred)->number > 1) {
11986             GameListPopUp(f, title);
11987             return TRUE;
11988         }
11989         GameListDestroy();
11990         n = 1;
11991     }
11992     if (n == 0) n = 1;
11993     return LoadGame(f, n, title, FALSE);
11994 }
11995
11996
11997 void
11998 MakeRegisteredMove ()
11999 {
12000     int fromX, fromY, toX, toY;
12001     char promoChar;
12002     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12003         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12004           case CMAIL_MOVE:
12005           case CMAIL_DRAW:
12006             if (appData.debugMode)
12007               fprintf(debugFP, "Restoring %s for game %d\n",
12008                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12009
12010             thinkOutput[0] = NULLCHAR;
12011             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12012             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12013             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12014             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12015             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12016             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12017             MakeMove(fromX, fromY, toX, toY, promoChar);
12018             ShowMove(fromX, fromY, toX, toY);
12019
12020             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12021               case MT_NONE:
12022               case MT_CHECK:
12023                 break;
12024
12025               case MT_CHECKMATE:
12026               case MT_STAINMATE:
12027                 if (WhiteOnMove(currentMove)) {
12028                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12029                 } else {
12030                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12031                 }
12032                 break;
12033
12034               case MT_STALEMATE:
12035                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12036                 break;
12037             }
12038
12039             break;
12040
12041           case CMAIL_RESIGN:
12042             if (WhiteOnMove(currentMove)) {
12043                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12044             } else {
12045                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12046             }
12047             break;
12048
12049           case CMAIL_ACCEPT:
12050             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12051             break;
12052
12053           default:
12054             break;
12055         }
12056     }
12057
12058     return;
12059 }
12060
12061 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12062 int
12063 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12064 {
12065     int retVal;
12066
12067     if (gameNumber > nCmailGames) {
12068         DisplayError(_("No more games in this message"), 0);
12069         return FALSE;
12070     }
12071     if (f == lastLoadGameFP) {
12072         int offset = gameNumber - lastLoadGameNumber;
12073         if (offset == 0) {
12074             cmailMsg[0] = NULLCHAR;
12075             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12076                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12077                 nCmailMovesRegistered--;
12078             }
12079             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12080             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12081                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12082             }
12083         } else {
12084             if (! RegisterMove()) return FALSE;
12085         }
12086     }
12087
12088     retVal = LoadGame(f, gameNumber, title, useList);
12089
12090     /* Make move registered during previous look at this game, if any */
12091     MakeRegisteredMove();
12092
12093     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12094         commentList[currentMove]
12095           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12096         DisplayComment(currentMove - 1, commentList[currentMove]);
12097     }
12098
12099     return retVal;
12100 }
12101
12102 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12103 int
12104 ReloadGame (int offset)
12105 {
12106     int gameNumber = lastLoadGameNumber + offset;
12107     if (lastLoadGameFP == NULL) {
12108         DisplayError(_("No game has been loaded yet"), 0);
12109         return FALSE;
12110     }
12111     if (gameNumber <= 0) {
12112         DisplayError(_("Can't back up any further"), 0);
12113         return FALSE;
12114     }
12115     if (cmailMsgLoaded) {
12116         return CmailLoadGame(lastLoadGameFP, gameNumber,
12117                              lastLoadGameTitle, lastLoadGameUseList);
12118     } else {
12119         return LoadGame(lastLoadGameFP, gameNumber,
12120                         lastLoadGameTitle, lastLoadGameUseList);
12121     }
12122 }
12123
12124 int keys[EmptySquare+1];
12125
12126 int
12127 PositionMatches (Board b1, Board b2)
12128 {
12129     int r, f, sum=0;
12130     switch(appData.searchMode) {
12131         case 1: return CompareWithRights(b1, b2);
12132         case 2:
12133             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12134                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12135             }
12136             return TRUE;
12137         case 3:
12138             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12139               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12140                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12141             }
12142             return sum==0;
12143         case 4:
12144             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12145                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12146             }
12147             return sum==0;
12148     }
12149     return TRUE;
12150 }
12151
12152 #define Q_PROMO  4
12153 #define Q_EP     3
12154 #define Q_BCASTL 2
12155 #define Q_WCASTL 1
12156
12157 int pieceList[256], quickBoard[256];
12158 ChessSquare pieceType[256] = { EmptySquare };
12159 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12160 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12161 int soughtTotal, turn;
12162 Boolean epOK, flipSearch;
12163
12164 typedef struct {
12165     unsigned char piece, to;
12166 } Move;
12167
12168 #define DSIZE (250000)
12169
12170 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12171 Move *moveDatabase = initialSpace;
12172 unsigned int movePtr, dataSize = DSIZE;
12173
12174 int
12175 MakePieceList (Board board, int *counts)
12176 {
12177     int r, f, n=Q_PROMO, total=0;
12178     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12179     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12180         int sq = f + (r<<4);
12181         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12182             quickBoard[sq] = ++n;
12183             pieceList[n] = sq;
12184             pieceType[n] = board[r][f];
12185             counts[board[r][f]]++;
12186             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12187             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12188             total++;
12189         }
12190     }
12191     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12192     return total;
12193 }
12194
12195 void
12196 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12197 {
12198     int sq = fromX + (fromY<<4);
12199     int piece = quickBoard[sq], rook;
12200     quickBoard[sq] = 0;
12201     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12202     if(piece == pieceList[1] && fromY == toY) {
12203       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12204         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12205         moveDatabase[movePtr++].piece = Q_WCASTL;
12206         quickBoard[sq] = piece;
12207         piece = quickBoard[from]; quickBoard[from] = 0;
12208         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12209       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12210         quickBoard[sq] = 0; // remove Rook
12211         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12212         moveDatabase[movePtr++].piece = Q_WCASTL;
12213         quickBoard[sq] = pieceList[1]; // put King
12214         piece = rook;
12215         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12216       }
12217     } else
12218     if(piece == pieceList[2] && fromY == toY) {
12219       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12220         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12221         moveDatabase[movePtr++].piece = Q_BCASTL;
12222         quickBoard[sq] = piece;
12223         piece = quickBoard[from]; quickBoard[from] = 0;
12224         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12225       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12226         quickBoard[sq] = 0; // remove Rook
12227         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12228         moveDatabase[movePtr++].piece = Q_BCASTL;
12229         quickBoard[sq] = pieceList[2]; // put King
12230         piece = rook;
12231         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12232       }
12233     } else
12234     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12235         quickBoard[(fromY<<4)+toX] = 0;
12236         moveDatabase[movePtr].piece = Q_EP;
12237         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12238         moveDatabase[movePtr].to = sq;
12239     } else
12240     if(promoPiece != pieceType[piece]) {
12241         moveDatabase[movePtr++].piece = Q_PROMO;
12242         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12243     }
12244     moveDatabase[movePtr].piece = piece;
12245     quickBoard[sq] = piece;
12246     movePtr++;
12247 }
12248
12249 int
12250 PackGame (Board board)
12251 {
12252     Move *newSpace = NULL;
12253     moveDatabase[movePtr].piece = 0; // terminate previous game
12254     if(movePtr > dataSize) {
12255         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12256         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12257         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12258         if(newSpace) {
12259             int i;
12260             Move *p = moveDatabase, *q = newSpace;
12261             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12262             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12263             moveDatabase = newSpace;
12264         } else { // calloc failed, we must be out of memory. Too bad...
12265             dataSize = 0; // prevent calloc events for all subsequent games
12266             return 0;     // and signal this one isn't cached
12267         }
12268     }
12269     movePtr++;
12270     MakePieceList(board, counts);
12271     return movePtr;
12272 }
12273
12274 int
12275 QuickCompare (Board board, int *minCounts, int *maxCounts)
12276 {   // compare according to search mode
12277     int r, f;
12278     switch(appData.searchMode)
12279     {
12280       case 1: // exact position match
12281         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12282         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12283             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12284         }
12285         break;
12286       case 2: // can have extra material on empty squares
12287         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12288             if(board[r][f] == EmptySquare) continue;
12289             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12290         }
12291         break;
12292       case 3: // material with exact Pawn structure
12293         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12294             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12295             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12296         } // fall through to material comparison
12297       case 4: // exact material
12298         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12299         break;
12300       case 6: // material range with given imbalance
12301         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12302         // fall through to range comparison
12303       case 5: // material range
12304         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12305     }
12306     return TRUE;
12307 }
12308
12309 int
12310 QuickScan (Board board, Move *move)
12311 {   // reconstruct game,and compare all positions in it
12312     int cnt=0, stretch=0, total = MakePieceList(board, counts);
12313     do {
12314         int piece = move->piece;
12315         int to = move->to, from = pieceList[piece];
12316         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12317           if(!piece) return -1;
12318           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12319             piece = (++move)->piece;
12320             from = pieceList[piece];
12321             counts[pieceType[piece]]--;
12322             pieceType[piece] = (ChessSquare) move->to;
12323             counts[move->to]++;
12324           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12325             counts[pieceType[quickBoard[to]]]--;
12326             quickBoard[to] = 0; total--;
12327             move++;
12328             continue;
12329           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12330             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12331             from  = pieceList[piece]; // so this must be King
12332             quickBoard[from] = 0;
12333             pieceList[piece] = to;
12334             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12335             quickBoard[from] = 0; // rook
12336             quickBoard[to] = piece;
12337             to = move->to; piece = move->piece;
12338             goto aftercastle;
12339           }
12340         }
12341         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12342         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
12343         quickBoard[from] = 0;
12344       aftercastle:
12345         quickBoard[to] = piece;
12346         pieceList[piece] = to;
12347         cnt++; turn ^= 3;
12348         if(QuickCompare(soughtBoard, minSought, maxSought) ||
12349            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12350            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12351                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12352           ) {
12353             static int lastCounts[EmptySquare+1];
12354             int i;
12355             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12356             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12357         } else stretch = 0;
12358         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
12359         move++;
12360     } while(1);
12361 }
12362
12363 void
12364 InitSearch ()
12365 {
12366     int r, f;
12367     flipSearch = FALSE;
12368     CopyBoard(soughtBoard, boards[currentMove]);
12369     soughtTotal = MakePieceList(soughtBoard, maxSought);
12370     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12371     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12372     CopyBoard(reverseBoard, boards[currentMove]);
12373     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12374         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12375         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12376         reverseBoard[r][f] = piece;
12377     }
12378     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12379     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12380     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12381                  || (boards[currentMove][CASTLING][2] == NoRights ||
12382                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12383                  && (boards[currentMove][CASTLING][5] == NoRights ||
12384                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12385       ) {
12386         flipSearch = TRUE;
12387         CopyBoard(flipBoard, soughtBoard);
12388         CopyBoard(rotateBoard, reverseBoard);
12389         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12390             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12391             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12392         }
12393     }
12394     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12395     if(appData.searchMode >= 5) {
12396         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12397         MakePieceList(soughtBoard, minSought);
12398         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12399     }
12400     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12401         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12402 }
12403
12404 GameInfo dummyInfo;
12405 static int creatingBook;
12406
12407 int
12408 GameContainsPosition (FILE *f, ListGame *lg)
12409 {
12410     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12411     int fromX, fromY, toX, toY;
12412     char promoChar;
12413     static int initDone=FALSE;
12414
12415     // weed out games based on numerical tag comparison
12416     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12417     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12418     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12419     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12420     if(!initDone) {
12421         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12422         initDone = TRUE;
12423     }
12424     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12425     else CopyBoard(boards[scratch], initialPosition); // default start position
12426     if(lg->moves) {
12427         turn = btm + 1;
12428         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12429         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12430     }
12431     if(btm) plyNr++;
12432     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12433     fseek(f, lg->offset, 0);
12434     yynewfile(f);
12435     while(1) {
12436         yyboardindex = scratch;
12437         quickFlag = plyNr+1;
12438         next = Myylex();
12439         quickFlag = 0;
12440         switch(next) {
12441             case PGNTag:
12442                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12443             default:
12444                 continue;
12445
12446             case XBoardGame:
12447             case GNUChessGame:
12448                 if(plyNr) return -1; // after we have seen moves, this is for new game
12449               continue;
12450
12451             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12452             case ImpossibleMove:
12453             case WhiteWins: // game ends here with these four
12454             case BlackWins:
12455             case GameIsDrawn:
12456             case GameUnfinished:
12457                 return -1;
12458
12459             case IllegalMove:
12460                 if(appData.testLegality) return -1;
12461             case WhiteCapturesEnPassant:
12462             case BlackCapturesEnPassant:
12463             case WhitePromotion:
12464             case BlackPromotion:
12465             case WhiteNonPromotion:
12466             case BlackNonPromotion:
12467             case NormalMove:
12468             case FirstLeg:
12469             case WhiteKingSideCastle:
12470             case WhiteQueenSideCastle:
12471             case BlackKingSideCastle:
12472             case BlackQueenSideCastle:
12473             case WhiteKingSideCastleWild:
12474             case WhiteQueenSideCastleWild:
12475             case BlackKingSideCastleWild:
12476             case BlackQueenSideCastleWild:
12477             case WhiteHSideCastleFR:
12478             case WhiteASideCastleFR:
12479             case BlackHSideCastleFR:
12480             case BlackASideCastleFR:
12481                 fromX = currentMoveString[0] - AAA;
12482                 fromY = currentMoveString[1] - ONE;
12483                 toX = currentMoveString[2] - AAA;
12484                 toY = currentMoveString[3] - ONE;
12485                 promoChar = currentMoveString[4];
12486                 break;
12487             case WhiteDrop:
12488             case BlackDrop:
12489                 fromX = next == WhiteDrop ?
12490                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12491                   (int) CharToPiece(ToLower(currentMoveString[0]));
12492                 fromY = DROP_RANK;
12493                 toX = currentMoveString[2] - AAA;
12494                 toY = currentMoveString[3] - ONE;
12495                 promoChar = 0;
12496                 break;
12497         }
12498         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12499         plyNr++;
12500         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12501         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12502         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12503         if(appData.findMirror) {
12504             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12505             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12506         }
12507     }
12508 }
12509
12510 /* Load the nth game from open file f */
12511 int
12512 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12513 {
12514     ChessMove cm;
12515     char buf[MSG_SIZ];
12516     int gn = gameNumber;
12517     ListGame *lg = NULL;
12518     int numPGNTags = 0;
12519     int err, pos = -1;
12520     GameMode oldGameMode;
12521     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12522
12523     if (appData.debugMode)
12524         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12525
12526     if (gameMode == Training )
12527         SetTrainingModeOff();
12528
12529     oldGameMode = gameMode;
12530     if (gameMode != BeginningOfGame) {
12531       Reset(FALSE, TRUE);
12532     }
12533     killX = killY = -1; // [HGM] lion: in case we did not Reset
12534
12535     gameFileFP = f;
12536     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12537         fclose(lastLoadGameFP);
12538     }
12539
12540     if (useList) {
12541         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12542
12543         if (lg) {
12544             fseek(f, lg->offset, 0);
12545             GameListHighlight(gameNumber);
12546             pos = lg->position;
12547             gn = 1;
12548         }
12549         else {
12550             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12551               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12552             else
12553             DisplayError(_("Game number out of range"), 0);
12554             return FALSE;
12555         }
12556     } else {
12557         GameListDestroy();
12558         if (fseek(f, 0, 0) == -1) {
12559             if (f == lastLoadGameFP ?
12560                 gameNumber == lastLoadGameNumber + 1 :
12561                 gameNumber == 1) {
12562                 gn = 1;
12563             } else {
12564                 DisplayError(_("Can't seek on game file"), 0);
12565                 return FALSE;
12566             }
12567         }
12568     }
12569     lastLoadGameFP = f;
12570     lastLoadGameNumber = gameNumber;
12571     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12572     lastLoadGameUseList = useList;
12573
12574     yynewfile(f);
12575
12576     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12577       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12578                 lg->gameInfo.black);
12579             DisplayTitle(buf);
12580     } else if (*title != NULLCHAR) {
12581         if (gameNumber > 1) {
12582           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12583             DisplayTitle(buf);
12584         } else {
12585             DisplayTitle(title);
12586         }
12587     }
12588
12589     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12590         gameMode = PlayFromGameFile;
12591         ModeHighlight();
12592     }
12593
12594     currentMove = forwardMostMove = backwardMostMove = 0;
12595     CopyBoard(boards[0], initialPosition);
12596     StopClocks();
12597
12598     /*
12599      * Skip the first gn-1 games in the file.
12600      * Also skip over anything that precedes an identifiable
12601      * start of game marker, to avoid being confused by
12602      * garbage at the start of the file.  Currently
12603      * recognized start of game markers are the move number "1",
12604      * the pattern "gnuchess .* game", the pattern
12605      * "^[#;%] [^ ]* game file", and a PGN tag block.
12606      * A game that starts with one of the latter two patterns
12607      * will also have a move number 1, possibly
12608      * following a position diagram.
12609      * 5-4-02: Let's try being more lenient and allowing a game to
12610      * start with an unnumbered move.  Does that break anything?
12611      */
12612     cm = lastLoadGameStart = EndOfFile;
12613     while (gn > 0) {
12614         yyboardindex = forwardMostMove;
12615         cm = (ChessMove) Myylex();
12616         switch (cm) {
12617           case EndOfFile:
12618             if (cmailMsgLoaded) {
12619                 nCmailGames = CMAIL_MAX_GAMES - gn;
12620             } else {
12621                 Reset(TRUE, TRUE);
12622                 DisplayError(_("Game not found in file"), 0);
12623             }
12624             return FALSE;
12625
12626           case GNUChessGame:
12627           case XBoardGame:
12628             gn--;
12629             lastLoadGameStart = cm;
12630             break;
12631
12632           case MoveNumberOne:
12633             switch (lastLoadGameStart) {
12634               case GNUChessGame:
12635               case XBoardGame:
12636               case PGNTag:
12637                 break;
12638               case MoveNumberOne:
12639               case EndOfFile:
12640                 gn--;           /* count this game */
12641                 lastLoadGameStart = cm;
12642                 break;
12643               default:
12644                 /* impossible */
12645                 break;
12646             }
12647             break;
12648
12649           case PGNTag:
12650             switch (lastLoadGameStart) {
12651               case GNUChessGame:
12652               case PGNTag:
12653               case MoveNumberOne:
12654               case EndOfFile:
12655                 gn--;           /* count this game */
12656                 lastLoadGameStart = cm;
12657                 break;
12658               case XBoardGame:
12659                 lastLoadGameStart = cm; /* game counted already */
12660                 break;
12661               default:
12662                 /* impossible */
12663                 break;
12664             }
12665             if (gn > 0) {
12666                 do {
12667                     yyboardindex = forwardMostMove;
12668                     cm = (ChessMove) Myylex();
12669                 } while (cm == PGNTag || cm == Comment);
12670             }
12671             break;
12672
12673           case WhiteWins:
12674           case BlackWins:
12675           case GameIsDrawn:
12676             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12677                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12678                     != CMAIL_OLD_RESULT) {
12679                     nCmailResults ++ ;
12680                     cmailResult[  CMAIL_MAX_GAMES
12681                                 - gn - 1] = CMAIL_OLD_RESULT;
12682                 }
12683             }
12684             break;
12685
12686           case NormalMove:
12687           case FirstLeg:
12688             /* Only a NormalMove can be at the start of a game
12689              * without a position diagram. */
12690             if (lastLoadGameStart == EndOfFile ) {
12691               gn--;
12692               lastLoadGameStart = MoveNumberOne;
12693             }
12694             break;
12695
12696           default:
12697             break;
12698         }
12699     }
12700
12701     if (appData.debugMode)
12702       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12703
12704     if (cm == XBoardGame) {
12705         /* Skip any header junk before position diagram and/or move 1 */
12706         for (;;) {
12707             yyboardindex = forwardMostMove;
12708             cm = (ChessMove) Myylex();
12709
12710             if (cm == EndOfFile ||
12711                 cm == GNUChessGame || cm == XBoardGame) {
12712                 /* Empty game; pretend end-of-file and handle later */
12713                 cm = EndOfFile;
12714                 break;
12715             }
12716
12717             if (cm == MoveNumberOne || cm == PositionDiagram ||
12718                 cm == PGNTag || cm == Comment)
12719               break;
12720         }
12721     } else if (cm == GNUChessGame) {
12722         if (gameInfo.event != NULL) {
12723             free(gameInfo.event);
12724         }
12725         gameInfo.event = StrSave(yy_text);
12726     }
12727
12728     startedFromSetupPosition = FALSE;
12729     while (cm == PGNTag) {
12730         if (appData.debugMode)
12731           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12732         err = ParsePGNTag(yy_text, &gameInfo);
12733         if (!err) numPGNTags++;
12734
12735         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12736         if(gameInfo.variant != oldVariant) {
12737             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12738             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12739             InitPosition(TRUE);
12740             oldVariant = gameInfo.variant;
12741             if (appData.debugMode)
12742               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12743         }
12744
12745
12746         if (gameInfo.fen != NULL) {
12747           Board initial_position;
12748           startedFromSetupPosition = TRUE;
12749           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12750             Reset(TRUE, TRUE);
12751             DisplayError(_("Bad FEN position in file"), 0);
12752             return FALSE;
12753           }
12754           CopyBoard(boards[0], initial_position);
12755           if (blackPlaysFirst) {
12756             currentMove = forwardMostMove = backwardMostMove = 1;
12757             CopyBoard(boards[1], initial_position);
12758             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12759             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12760             timeRemaining[0][1] = whiteTimeRemaining;
12761             timeRemaining[1][1] = blackTimeRemaining;
12762             if (commentList[0] != NULL) {
12763               commentList[1] = commentList[0];
12764               commentList[0] = NULL;
12765             }
12766           } else {
12767             currentMove = forwardMostMove = backwardMostMove = 0;
12768           }
12769           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12770           {   int i;
12771               initialRulePlies = FENrulePlies;
12772               for( i=0; i< nrCastlingRights; i++ )
12773                   initialRights[i] = initial_position[CASTLING][i];
12774           }
12775           yyboardindex = forwardMostMove;
12776           free(gameInfo.fen);
12777           gameInfo.fen = NULL;
12778         }
12779
12780         yyboardindex = forwardMostMove;
12781         cm = (ChessMove) Myylex();
12782
12783         /* Handle comments interspersed among the tags */
12784         while (cm == Comment) {
12785             char *p;
12786             if (appData.debugMode)
12787               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12788             p = yy_text;
12789             AppendComment(currentMove, p, FALSE);
12790             yyboardindex = forwardMostMove;
12791             cm = (ChessMove) Myylex();
12792         }
12793     }
12794
12795     /* don't rely on existence of Event tag since if game was
12796      * pasted from clipboard the Event tag may not exist
12797      */
12798     if (numPGNTags > 0){
12799         char *tags;
12800         if (gameInfo.variant == VariantNormal) {
12801           VariantClass v = StringToVariant(gameInfo.event);
12802           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12803           if(v < VariantShogi) gameInfo.variant = v;
12804         }
12805         if (!matchMode) {
12806           if( appData.autoDisplayTags ) {
12807             tags = PGNTags(&gameInfo);
12808             TagsPopUp(tags, CmailMsg());
12809             free(tags);
12810           }
12811         }
12812     } else {
12813         /* Make something up, but don't display it now */
12814         SetGameInfo();
12815         TagsPopDown();
12816     }
12817
12818     if (cm == PositionDiagram) {
12819         int i, j;
12820         char *p;
12821         Board initial_position;
12822
12823         if (appData.debugMode)
12824           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12825
12826         if (!startedFromSetupPosition) {
12827             p = yy_text;
12828             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12829               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12830                 switch (*p) {
12831                   case '{':
12832                   case '[':
12833                   case '-':
12834                   case ' ':
12835                   case '\t':
12836                   case '\n':
12837                   case '\r':
12838                     break;
12839                   default:
12840                     initial_position[i][j++] = CharToPiece(*p);
12841                     break;
12842                 }
12843             while (*p == ' ' || *p == '\t' ||
12844                    *p == '\n' || *p == '\r') p++;
12845
12846             if (strncmp(p, "black", strlen("black"))==0)
12847               blackPlaysFirst = TRUE;
12848             else
12849               blackPlaysFirst = FALSE;
12850             startedFromSetupPosition = TRUE;
12851
12852             CopyBoard(boards[0], initial_position);
12853             if (blackPlaysFirst) {
12854                 currentMove = forwardMostMove = backwardMostMove = 1;
12855                 CopyBoard(boards[1], initial_position);
12856                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12857                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12858                 timeRemaining[0][1] = whiteTimeRemaining;
12859                 timeRemaining[1][1] = blackTimeRemaining;
12860                 if (commentList[0] != NULL) {
12861                     commentList[1] = commentList[0];
12862                     commentList[0] = NULL;
12863                 }
12864             } else {
12865                 currentMove = forwardMostMove = backwardMostMove = 0;
12866             }
12867         }
12868         yyboardindex = forwardMostMove;
12869         cm = (ChessMove) Myylex();
12870     }
12871
12872   if(!creatingBook) {
12873     if (first.pr == NoProc) {
12874         StartChessProgram(&first);
12875     }
12876     InitChessProgram(&first, FALSE);
12877     SendToProgram("force\n", &first);
12878     if (startedFromSetupPosition) {
12879         SendBoard(&first, forwardMostMove);
12880     if (appData.debugMode) {
12881         fprintf(debugFP, "Load Game\n");
12882     }
12883         DisplayBothClocks();
12884     }
12885   }
12886
12887     /* [HGM] server: flag to write setup moves in broadcast file as one */
12888     loadFlag = appData.suppressLoadMoves;
12889
12890     while (cm == Comment) {
12891         char *p;
12892         if (appData.debugMode)
12893           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12894         p = yy_text;
12895         AppendComment(currentMove, p, FALSE);
12896         yyboardindex = forwardMostMove;
12897         cm = (ChessMove) Myylex();
12898     }
12899
12900     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12901         cm == WhiteWins || cm == BlackWins ||
12902         cm == GameIsDrawn || cm == GameUnfinished) {
12903         DisplayMessage("", _("No moves in game"));
12904         if (cmailMsgLoaded) {
12905             if (appData.debugMode)
12906               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12907             ClearHighlights();
12908             flipView = FALSE;
12909         }
12910         DrawPosition(FALSE, boards[currentMove]);
12911         DisplayBothClocks();
12912         gameMode = EditGame;
12913         ModeHighlight();
12914         gameFileFP = NULL;
12915         cmailOldMove = 0;
12916         return TRUE;
12917     }
12918
12919     // [HGM] PV info: routine tests if comment empty
12920     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12921         DisplayComment(currentMove - 1, commentList[currentMove]);
12922     }
12923     if (!matchMode && appData.timeDelay != 0)
12924       DrawPosition(FALSE, boards[currentMove]);
12925
12926     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12927       programStats.ok_to_send = 1;
12928     }
12929
12930     /* if the first token after the PGN tags is a move
12931      * and not move number 1, retrieve it from the parser
12932      */
12933     if (cm != MoveNumberOne)
12934         LoadGameOneMove(cm);
12935
12936     /* load the remaining moves from the file */
12937     while (LoadGameOneMove(EndOfFile)) {
12938       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12939       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12940     }
12941
12942     /* rewind to the start of the game */
12943     currentMove = backwardMostMove;
12944
12945     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12946
12947     if (oldGameMode == AnalyzeFile) {
12948       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
12949       AnalyzeFileEvent();
12950     } else
12951     if (oldGameMode == AnalyzeMode) {
12952       AnalyzeFileEvent();
12953     }
12954
12955     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
12956         long int w, b; // [HGM] adjourn: restore saved clock times
12957         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
12958         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
12959             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
12960             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
12961         }
12962     }
12963
12964     if(creatingBook) return TRUE;
12965     if (!matchMode && pos > 0) {
12966         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12967     } else
12968     if (matchMode || appData.timeDelay == 0) {
12969       ToEndEvent();
12970     } else if (appData.timeDelay > 0) {
12971       AutoPlayGameLoop();
12972     }
12973
12974     if (appData.debugMode)
12975         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12976
12977     loadFlag = 0; /* [HGM] true game starts */
12978     return TRUE;
12979 }
12980
12981 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12982 int
12983 ReloadPosition (int offset)
12984 {
12985     int positionNumber = lastLoadPositionNumber + offset;
12986     if (lastLoadPositionFP == NULL) {
12987         DisplayError(_("No position has been loaded yet"), 0);
12988         return FALSE;
12989     }
12990     if (positionNumber <= 0) {
12991         DisplayError(_("Can't back up any further"), 0);
12992         return FALSE;
12993     }
12994     return LoadPosition(lastLoadPositionFP, positionNumber,
12995                         lastLoadPositionTitle);
12996 }
12997
12998 /* Load the nth position from the given file */
12999 int
13000 LoadPositionFromFile (char *filename, int n, char *title)
13001 {
13002     FILE *f;
13003     char buf[MSG_SIZ];
13004
13005     if (strcmp(filename, "-") == 0) {
13006         return LoadPosition(stdin, n, "stdin");
13007     } else {
13008         f = fopen(filename, "rb");
13009         if (f == NULL) {
13010             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13011             DisplayError(buf, errno);
13012             return FALSE;
13013         } else {
13014             return LoadPosition(f, n, title);
13015         }
13016     }
13017 }
13018
13019 /* Load the nth position from the given open file, and close it */
13020 int
13021 LoadPosition (FILE *f, int positionNumber, char *title)
13022 {
13023     char *p, line[MSG_SIZ];
13024     Board initial_position;
13025     int i, j, fenMode, pn;
13026
13027     if (gameMode == Training )
13028         SetTrainingModeOff();
13029
13030     if (gameMode != BeginningOfGame) {
13031         Reset(FALSE, TRUE);
13032     }
13033     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13034         fclose(lastLoadPositionFP);
13035     }
13036     if (positionNumber == 0) positionNumber = 1;
13037     lastLoadPositionFP = f;
13038     lastLoadPositionNumber = positionNumber;
13039     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13040     if (first.pr == NoProc && !appData.noChessProgram) {
13041       StartChessProgram(&first);
13042       InitChessProgram(&first, FALSE);
13043     }
13044     pn = positionNumber;
13045     if (positionNumber < 0) {
13046         /* Negative position number means to seek to that byte offset */
13047         if (fseek(f, -positionNumber, 0) == -1) {
13048             DisplayError(_("Can't seek on position file"), 0);
13049             return FALSE;
13050         };
13051         pn = 1;
13052     } else {
13053         if (fseek(f, 0, 0) == -1) {
13054             if (f == lastLoadPositionFP ?
13055                 positionNumber == lastLoadPositionNumber + 1 :
13056                 positionNumber == 1) {
13057                 pn = 1;
13058             } else {
13059                 DisplayError(_("Can't seek on position file"), 0);
13060                 return FALSE;
13061             }
13062         }
13063     }
13064     /* See if this file is FEN or old-style xboard */
13065     if (fgets(line, MSG_SIZ, f) == NULL) {
13066         DisplayError(_("Position not found in file"), 0);
13067         return FALSE;
13068     }
13069     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13070     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13071
13072     if (pn >= 2) {
13073         if (fenMode || line[0] == '#') pn--;
13074         while (pn > 0) {
13075             /* skip positions before number pn */
13076             if (fgets(line, MSG_SIZ, f) == NULL) {
13077                 Reset(TRUE, TRUE);
13078                 DisplayError(_("Position not found in file"), 0);
13079                 return FALSE;
13080             }
13081             if (fenMode || line[0] == '#') pn--;
13082         }
13083     }
13084
13085     if (fenMode) {
13086         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13087             DisplayError(_("Bad FEN position in file"), 0);
13088             return FALSE;
13089         }
13090     } else {
13091         (void) fgets(line, MSG_SIZ, f);
13092         (void) fgets(line, MSG_SIZ, f);
13093
13094         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13095             (void) fgets(line, MSG_SIZ, f);
13096             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13097                 if (*p == ' ')
13098                   continue;
13099                 initial_position[i][j++] = CharToPiece(*p);
13100             }
13101         }
13102
13103         blackPlaysFirst = FALSE;
13104         if (!feof(f)) {
13105             (void) fgets(line, MSG_SIZ, f);
13106             if (strncmp(line, "black", strlen("black"))==0)
13107               blackPlaysFirst = TRUE;
13108         }
13109     }
13110     startedFromSetupPosition = TRUE;
13111
13112     CopyBoard(boards[0], initial_position);
13113     if (blackPlaysFirst) {
13114         currentMove = forwardMostMove = backwardMostMove = 1;
13115         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13116         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13117         CopyBoard(boards[1], initial_position);
13118         DisplayMessage("", _("Black to play"));
13119     } else {
13120         currentMove = forwardMostMove = backwardMostMove = 0;
13121         DisplayMessage("", _("White to play"));
13122     }
13123     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13124     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13125         SendToProgram("force\n", &first);
13126         SendBoard(&first, forwardMostMove);
13127     }
13128     if (appData.debugMode) {
13129 int i, j;
13130   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13131   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13132         fprintf(debugFP, "Load Position\n");
13133     }
13134
13135     if (positionNumber > 1) {
13136       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13137         DisplayTitle(line);
13138     } else {
13139         DisplayTitle(title);
13140     }
13141     gameMode = EditGame;
13142     ModeHighlight();
13143     ResetClocks();
13144     timeRemaining[0][1] = whiteTimeRemaining;
13145     timeRemaining[1][1] = blackTimeRemaining;
13146     DrawPosition(FALSE, boards[currentMove]);
13147
13148     return TRUE;
13149 }
13150
13151
13152 void
13153 CopyPlayerNameIntoFileName (char **dest, char *src)
13154 {
13155     while (*src != NULLCHAR && *src != ',') {
13156         if (*src == ' ') {
13157             *(*dest)++ = '_';
13158             src++;
13159         } else {
13160             *(*dest)++ = *src++;
13161         }
13162     }
13163 }
13164
13165 char *
13166 DefaultFileName (char *ext)
13167 {
13168     static char def[MSG_SIZ];
13169     char *p;
13170
13171     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13172         p = def;
13173         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13174         *p++ = '-';
13175         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13176         *p++ = '.';
13177         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13178     } else {
13179         def[0] = NULLCHAR;
13180     }
13181     return def;
13182 }
13183
13184 /* Save the current game to the given file */
13185 int
13186 SaveGameToFile (char *filename, int append)
13187 {
13188     FILE *f;
13189     char buf[MSG_SIZ];
13190     int result, i, t,tot=0;
13191
13192     if (strcmp(filename, "-") == 0) {
13193         return SaveGame(stdout, 0, NULL);
13194     } else {
13195         for(i=0; i<10; i++) { // upto 10 tries
13196              f = fopen(filename, append ? "a" : "w");
13197              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13198              if(f || errno != 13) break;
13199              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13200              tot += t;
13201         }
13202         if (f == NULL) {
13203             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13204             DisplayError(buf, errno);
13205             return FALSE;
13206         } else {
13207             safeStrCpy(buf, lastMsg, MSG_SIZ);
13208             DisplayMessage(_("Waiting for access to save file"), "");
13209             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13210             DisplayMessage(_("Saving game"), "");
13211             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13212             result = SaveGame(f, 0, NULL);
13213             DisplayMessage(buf, "");
13214             return result;
13215         }
13216     }
13217 }
13218
13219 char *
13220 SavePart (char *str)
13221 {
13222     static char buf[MSG_SIZ];
13223     char *p;
13224
13225     p = strchr(str, ' ');
13226     if (p == NULL) return str;
13227     strncpy(buf, str, p - str);
13228     buf[p - str] = NULLCHAR;
13229     return buf;
13230 }
13231
13232 #define PGN_MAX_LINE 75
13233
13234 #define PGN_SIDE_WHITE  0
13235 #define PGN_SIDE_BLACK  1
13236
13237 static int
13238 FindFirstMoveOutOfBook (int side)
13239 {
13240     int result = -1;
13241
13242     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13243         int index = backwardMostMove;
13244         int has_book_hit = 0;
13245
13246         if( (index % 2) != side ) {
13247             index++;
13248         }
13249
13250         while( index < forwardMostMove ) {
13251             /* Check to see if engine is in book */
13252             int depth = pvInfoList[index].depth;
13253             int score = pvInfoList[index].score;
13254             int in_book = 0;
13255
13256             if( depth <= 2 ) {
13257                 in_book = 1;
13258             }
13259             else if( score == 0 && depth == 63 ) {
13260                 in_book = 1; /* Zappa */
13261             }
13262             else if( score == 2 && depth == 99 ) {
13263                 in_book = 1; /* Abrok */
13264             }
13265
13266             has_book_hit += in_book;
13267
13268             if( ! in_book ) {
13269                 result = index;
13270
13271                 break;
13272             }
13273
13274             index += 2;
13275         }
13276     }
13277
13278     return result;
13279 }
13280
13281 void
13282 GetOutOfBookInfo (char * buf)
13283 {
13284     int oob[2];
13285     int i;
13286     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13287
13288     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13289     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13290
13291     *buf = '\0';
13292
13293     if( oob[0] >= 0 || oob[1] >= 0 ) {
13294         for( i=0; i<2; i++ ) {
13295             int idx = oob[i];
13296
13297             if( idx >= 0 ) {
13298                 if( i > 0 && oob[0] >= 0 ) {
13299                     strcat( buf, "   " );
13300                 }
13301
13302                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13303                 sprintf( buf+strlen(buf), "%s%.2f",
13304                     pvInfoList[idx].score >= 0 ? "+" : "",
13305                     pvInfoList[idx].score / 100.0 );
13306             }
13307         }
13308     }
13309 }
13310
13311 /* Save game in PGN style and close the file */
13312 int
13313 SaveGamePGN (FILE *f)
13314 {
13315     int i, offset, linelen, newblock;
13316 //    char *movetext;
13317     char numtext[32];
13318     int movelen, numlen, blank;
13319     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13320
13321     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13322
13323     PrintPGNTags(f, &gameInfo);
13324
13325     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13326
13327     if (backwardMostMove > 0 || startedFromSetupPosition) {
13328         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13329         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13330         fprintf(f, "\n{--------------\n");
13331         PrintPosition(f, backwardMostMove);
13332         fprintf(f, "--------------}\n");
13333         free(fen);
13334     }
13335     else {
13336         /* [AS] Out of book annotation */
13337         if( appData.saveOutOfBookInfo ) {
13338             char buf[64];
13339
13340             GetOutOfBookInfo( buf );
13341
13342             if( buf[0] != '\0' ) {
13343                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13344             }
13345         }
13346
13347         fprintf(f, "\n");
13348     }
13349
13350     i = backwardMostMove;
13351     linelen = 0;
13352     newblock = TRUE;
13353
13354     while (i < forwardMostMove) {
13355         /* Print comments preceding this move */
13356         if (commentList[i] != NULL) {
13357             if (linelen > 0) fprintf(f, "\n");
13358             fprintf(f, "%s", commentList[i]);
13359             linelen = 0;
13360             newblock = TRUE;
13361         }
13362
13363         /* Format move number */
13364         if ((i % 2) == 0)
13365           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13366         else
13367           if (newblock)
13368             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13369           else
13370             numtext[0] = NULLCHAR;
13371
13372         numlen = strlen(numtext);
13373         newblock = FALSE;
13374
13375         /* Print move number */
13376         blank = linelen > 0 && numlen > 0;
13377         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13378             fprintf(f, "\n");
13379             linelen = 0;
13380             blank = 0;
13381         }
13382         if (blank) {
13383             fprintf(f, " ");
13384             linelen++;
13385         }
13386         fprintf(f, "%s", numtext);
13387         linelen += numlen;
13388
13389         /* Get move */
13390         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13391         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13392
13393         /* Print move */
13394         blank = linelen > 0 && movelen > 0;
13395         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13396             fprintf(f, "\n");
13397             linelen = 0;
13398             blank = 0;
13399         }
13400         if (blank) {
13401             fprintf(f, " ");
13402             linelen++;
13403         }
13404         fprintf(f, "%s", move_buffer);
13405         linelen += movelen;
13406
13407         /* [AS] Add PV info if present */
13408         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13409             /* [HGM] add time */
13410             char buf[MSG_SIZ]; int seconds;
13411
13412             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13413
13414             if( seconds <= 0)
13415               buf[0] = 0;
13416             else
13417               if( seconds < 30 )
13418                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13419               else
13420                 {
13421                   seconds = (seconds + 4)/10; // round to full seconds
13422                   if( seconds < 60 )
13423                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13424                   else
13425                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13426                 }
13427
13428             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13429                       pvInfoList[i].score >= 0 ? "+" : "",
13430                       pvInfoList[i].score / 100.0,
13431                       pvInfoList[i].depth,
13432                       buf );
13433
13434             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13435
13436             /* Print score/depth */
13437             blank = linelen > 0 && movelen > 0;
13438             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13439                 fprintf(f, "\n");
13440                 linelen = 0;
13441                 blank = 0;
13442             }
13443             if (blank) {
13444                 fprintf(f, " ");
13445                 linelen++;
13446             }
13447             fprintf(f, "%s", move_buffer);
13448             linelen += movelen;
13449         }
13450
13451         i++;
13452     }
13453
13454     /* Start a new line */
13455     if (linelen > 0) fprintf(f, "\n");
13456
13457     /* Print comments after last move */
13458     if (commentList[i] != NULL) {
13459         fprintf(f, "%s\n", commentList[i]);
13460     }
13461
13462     /* Print result */
13463     if (gameInfo.resultDetails != NULL &&
13464         gameInfo.resultDetails[0] != NULLCHAR) {
13465         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13466         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13467            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13468             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13469         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13470     } else {
13471         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13472     }
13473
13474     fclose(f);
13475     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13476     return TRUE;
13477 }
13478
13479 /* Save game in old style and close the file */
13480 int
13481 SaveGameOldStyle (FILE *f)
13482 {
13483     int i, offset;
13484     time_t tm;
13485
13486     tm = time((time_t *) NULL);
13487
13488     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13489     PrintOpponents(f);
13490
13491     if (backwardMostMove > 0 || startedFromSetupPosition) {
13492         fprintf(f, "\n[--------------\n");
13493         PrintPosition(f, backwardMostMove);
13494         fprintf(f, "--------------]\n");
13495     } else {
13496         fprintf(f, "\n");
13497     }
13498
13499     i = backwardMostMove;
13500     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13501
13502     while (i < forwardMostMove) {
13503         if (commentList[i] != NULL) {
13504             fprintf(f, "[%s]\n", commentList[i]);
13505         }
13506
13507         if ((i % 2) == 1) {
13508             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13509             i++;
13510         } else {
13511             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13512             i++;
13513             if (commentList[i] != NULL) {
13514                 fprintf(f, "\n");
13515                 continue;
13516             }
13517             if (i >= forwardMostMove) {
13518                 fprintf(f, "\n");
13519                 break;
13520             }
13521             fprintf(f, "%s\n", parseList[i]);
13522             i++;
13523         }
13524     }
13525
13526     if (commentList[i] != NULL) {
13527         fprintf(f, "[%s]\n", commentList[i]);
13528     }
13529
13530     /* This isn't really the old style, but it's close enough */
13531     if (gameInfo.resultDetails != NULL &&
13532         gameInfo.resultDetails[0] != NULLCHAR) {
13533         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13534                 gameInfo.resultDetails);
13535     } else {
13536         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13537     }
13538
13539     fclose(f);
13540     return TRUE;
13541 }
13542
13543 /* Save the current game to open file f and close the file */
13544 int
13545 SaveGame (FILE *f, int dummy, char *dummy2)
13546 {
13547     if (gameMode == EditPosition) EditPositionDone(TRUE);
13548     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13549     if (appData.oldSaveStyle)
13550       return SaveGameOldStyle(f);
13551     else
13552       return SaveGamePGN(f);
13553 }
13554
13555 /* Save the current position to the given file */
13556 int
13557 SavePositionToFile (char *filename)
13558 {
13559     FILE *f;
13560     char buf[MSG_SIZ];
13561
13562     if (strcmp(filename, "-") == 0) {
13563         return SavePosition(stdout, 0, NULL);
13564     } else {
13565         f = fopen(filename, "a");
13566         if (f == NULL) {
13567             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13568             DisplayError(buf, errno);
13569             return FALSE;
13570         } else {
13571             safeStrCpy(buf, lastMsg, MSG_SIZ);
13572             DisplayMessage(_("Waiting for access to save file"), "");
13573             flock(fileno(f), LOCK_EX); // [HGM] lock
13574             DisplayMessage(_("Saving position"), "");
13575             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13576             SavePosition(f, 0, NULL);
13577             DisplayMessage(buf, "");
13578             return TRUE;
13579         }
13580     }
13581 }
13582
13583 /* Save the current position to the given open file and close the file */
13584 int
13585 SavePosition (FILE *f, int dummy, char *dummy2)
13586 {
13587     time_t tm;
13588     char *fen;
13589
13590     if (gameMode == EditPosition) EditPositionDone(TRUE);
13591     if (appData.oldSaveStyle) {
13592         tm = time((time_t *) NULL);
13593
13594         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13595         PrintOpponents(f);
13596         fprintf(f, "[--------------\n");
13597         PrintPosition(f, currentMove);
13598         fprintf(f, "--------------]\n");
13599     } else {
13600         fen = PositionToFEN(currentMove, NULL, 1);
13601         fprintf(f, "%s\n", fen);
13602         free(fen);
13603     }
13604     fclose(f);
13605     return TRUE;
13606 }
13607
13608 void
13609 ReloadCmailMsgEvent (int unregister)
13610 {
13611 #if !WIN32
13612     static char *inFilename = NULL;
13613     static char *outFilename;
13614     int i;
13615     struct stat inbuf, outbuf;
13616     int status;
13617
13618     /* Any registered moves are unregistered if unregister is set, */
13619     /* i.e. invoked by the signal handler */
13620     if (unregister) {
13621         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13622             cmailMoveRegistered[i] = FALSE;
13623             if (cmailCommentList[i] != NULL) {
13624                 free(cmailCommentList[i]);
13625                 cmailCommentList[i] = NULL;
13626             }
13627         }
13628         nCmailMovesRegistered = 0;
13629     }
13630
13631     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13632         cmailResult[i] = CMAIL_NOT_RESULT;
13633     }
13634     nCmailResults = 0;
13635
13636     if (inFilename == NULL) {
13637         /* Because the filenames are static they only get malloced once  */
13638         /* and they never get freed                                      */
13639         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13640         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13641
13642         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13643         sprintf(outFilename, "%s.out", appData.cmailGameName);
13644     }
13645
13646     status = stat(outFilename, &outbuf);
13647     if (status < 0) {
13648         cmailMailedMove = FALSE;
13649     } else {
13650         status = stat(inFilename, &inbuf);
13651         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13652     }
13653
13654     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13655        counts the games, notes how each one terminated, etc.
13656
13657        It would be nice to remove this kludge and instead gather all
13658        the information while building the game list.  (And to keep it
13659        in the game list nodes instead of having a bunch of fixed-size
13660        parallel arrays.)  Note this will require getting each game's
13661        termination from the PGN tags, as the game list builder does
13662        not process the game moves.  --mann
13663        */
13664     cmailMsgLoaded = TRUE;
13665     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13666
13667     /* Load first game in the file or popup game menu */
13668     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13669
13670 #endif /* !WIN32 */
13671     return;
13672 }
13673
13674 int
13675 RegisterMove ()
13676 {
13677     FILE *f;
13678     char string[MSG_SIZ];
13679
13680     if (   cmailMailedMove
13681         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13682         return TRUE;            /* Allow free viewing  */
13683     }
13684
13685     /* Unregister move to ensure that we don't leave RegisterMove        */
13686     /* with the move registered when the conditions for registering no   */
13687     /* longer hold                                                       */
13688     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13689         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13690         nCmailMovesRegistered --;
13691
13692         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13693           {
13694               free(cmailCommentList[lastLoadGameNumber - 1]);
13695               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13696           }
13697     }
13698
13699     if (cmailOldMove == -1) {
13700         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13701         return FALSE;
13702     }
13703
13704     if (currentMove > cmailOldMove + 1) {
13705         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13706         return FALSE;
13707     }
13708
13709     if (currentMove < cmailOldMove) {
13710         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13711         return FALSE;
13712     }
13713
13714     if (forwardMostMove > currentMove) {
13715         /* Silently truncate extra moves */
13716         TruncateGame();
13717     }
13718
13719     if (   (currentMove == cmailOldMove + 1)
13720         || (   (currentMove == cmailOldMove)
13721             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13722                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13723         if (gameInfo.result != GameUnfinished) {
13724             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13725         }
13726
13727         if (commentList[currentMove] != NULL) {
13728             cmailCommentList[lastLoadGameNumber - 1]
13729               = StrSave(commentList[currentMove]);
13730         }
13731         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13732
13733         if (appData.debugMode)
13734           fprintf(debugFP, "Saving %s for game %d\n",
13735                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13736
13737         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13738
13739         f = fopen(string, "w");
13740         if (appData.oldSaveStyle) {
13741             SaveGameOldStyle(f); /* also closes the file */
13742
13743             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13744             f = fopen(string, "w");
13745             SavePosition(f, 0, NULL); /* also closes the file */
13746         } else {
13747             fprintf(f, "{--------------\n");
13748             PrintPosition(f, currentMove);
13749             fprintf(f, "--------------}\n\n");
13750
13751             SaveGame(f, 0, NULL); /* also closes the file*/
13752         }
13753
13754         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13755         nCmailMovesRegistered ++;
13756     } else if (nCmailGames == 1) {
13757         DisplayError(_("You have not made a move yet"), 0);
13758         return FALSE;
13759     }
13760
13761     return TRUE;
13762 }
13763
13764 void
13765 MailMoveEvent ()
13766 {
13767 #if !WIN32
13768     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13769     FILE *commandOutput;
13770     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13771     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13772     int nBuffers;
13773     int i;
13774     int archived;
13775     char *arcDir;
13776
13777     if (! cmailMsgLoaded) {
13778         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13779         return;
13780     }
13781
13782     if (nCmailGames == nCmailResults) {
13783         DisplayError(_("No unfinished games"), 0);
13784         return;
13785     }
13786
13787 #if CMAIL_PROHIBIT_REMAIL
13788     if (cmailMailedMove) {
13789       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);
13790         DisplayError(msg, 0);
13791         return;
13792     }
13793 #endif
13794
13795     if (! (cmailMailedMove || RegisterMove())) return;
13796
13797     if (   cmailMailedMove
13798         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13799       snprintf(string, MSG_SIZ, partCommandString,
13800                appData.debugMode ? " -v" : "", appData.cmailGameName);
13801         commandOutput = popen(string, "r");
13802
13803         if (commandOutput == NULL) {
13804             DisplayError(_("Failed to invoke cmail"), 0);
13805         } else {
13806             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13807                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13808             }
13809             if (nBuffers > 1) {
13810                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13811                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13812                 nBytes = MSG_SIZ - 1;
13813             } else {
13814                 (void) memcpy(msg, buffer, nBytes);
13815             }
13816             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13817
13818             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13819                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13820
13821                 archived = TRUE;
13822                 for (i = 0; i < nCmailGames; i ++) {
13823                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13824                         archived = FALSE;
13825                     }
13826                 }
13827                 if (   archived
13828                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13829                         != NULL)) {
13830                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13831                            arcDir,
13832                            appData.cmailGameName,
13833                            gameInfo.date);
13834                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13835                     cmailMsgLoaded = FALSE;
13836                 }
13837             }
13838
13839             DisplayInformation(msg);
13840             pclose(commandOutput);
13841         }
13842     } else {
13843         if ((*cmailMsg) != '\0') {
13844             DisplayInformation(cmailMsg);
13845         }
13846     }
13847
13848     return;
13849 #endif /* !WIN32 */
13850 }
13851
13852 char *
13853 CmailMsg ()
13854 {
13855 #if WIN32
13856     return NULL;
13857 #else
13858     int  prependComma = 0;
13859     char number[5];
13860     char string[MSG_SIZ];       /* Space for game-list */
13861     int  i;
13862
13863     if (!cmailMsgLoaded) return "";
13864
13865     if (cmailMailedMove) {
13866       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13867     } else {
13868         /* Create a list of games left */
13869       snprintf(string, MSG_SIZ, "[");
13870         for (i = 0; i < nCmailGames; i ++) {
13871             if (! (   cmailMoveRegistered[i]
13872                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13873                 if (prependComma) {
13874                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13875                 } else {
13876                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13877                     prependComma = 1;
13878                 }
13879
13880                 strcat(string, number);
13881             }
13882         }
13883         strcat(string, "]");
13884
13885         if (nCmailMovesRegistered + nCmailResults == 0) {
13886             switch (nCmailGames) {
13887               case 1:
13888                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13889                 break;
13890
13891               case 2:
13892                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13893                 break;
13894
13895               default:
13896                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13897                          nCmailGames);
13898                 break;
13899             }
13900         } else {
13901             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13902               case 1:
13903                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13904                          string);
13905                 break;
13906
13907               case 0:
13908                 if (nCmailResults == nCmailGames) {
13909                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13910                 } else {
13911                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13912                 }
13913                 break;
13914
13915               default:
13916                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13917                          string);
13918             }
13919         }
13920     }
13921     return cmailMsg;
13922 #endif /* WIN32 */
13923 }
13924
13925 void
13926 ResetGameEvent ()
13927 {
13928     if (gameMode == Training)
13929       SetTrainingModeOff();
13930
13931     Reset(TRUE, TRUE);
13932     cmailMsgLoaded = FALSE;
13933     if (appData.icsActive) {
13934       SendToICS(ics_prefix);
13935       SendToICS("refresh\n");
13936     }
13937 }
13938
13939 void
13940 ExitEvent (int status)
13941 {
13942     exiting++;
13943     if (exiting > 2) {
13944       /* Give up on clean exit */
13945       exit(status);
13946     }
13947     if (exiting > 1) {
13948       /* Keep trying for clean exit */
13949       return;
13950     }
13951
13952     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13953
13954     if (telnetISR != NULL) {
13955       RemoveInputSource(telnetISR);
13956     }
13957     if (icsPR != NoProc) {
13958       DestroyChildProcess(icsPR, TRUE);
13959     }
13960
13961     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13962     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13963
13964     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13965     /* make sure this other one finishes before killing it!                  */
13966     if(endingGame) { int count = 0;
13967         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13968         while(endingGame && count++ < 10) DoSleep(1);
13969         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13970     }
13971
13972     /* Kill off chess programs */
13973     if (first.pr != NoProc) {
13974         ExitAnalyzeMode();
13975
13976         DoSleep( appData.delayBeforeQuit );
13977         SendToProgram("quit\n", &first);
13978         DoSleep( appData.delayAfterQuit );
13979         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13980     }
13981     if (second.pr != NoProc) {
13982         DoSleep( appData.delayBeforeQuit );
13983         SendToProgram("quit\n", &second);
13984         DoSleep( appData.delayAfterQuit );
13985         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13986     }
13987     if (first.isr != NULL) {
13988         RemoveInputSource(first.isr);
13989     }
13990     if (second.isr != NULL) {
13991         RemoveInputSource(second.isr);
13992     }
13993
13994     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13995     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13996
13997     ShutDownFrontEnd();
13998     exit(status);
13999 }
14000
14001 void
14002 PauseEngine (ChessProgramState *cps)
14003 {
14004     SendToProgram("pause\n", cps);
14005     cps->pause = 2;
14006 }
14007
14008 void
14009 UnPauseEngine (ChessProgramState *cps)
14010 {
14011     SendToProgram("resume\n", cps);
14012     cps->pause = 1;
14013 }
14014
14015 void
14016 PauseEvent ()
14017 {
14018     if (appData.debugMode)
14019         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14020     if (pausing) {
14021         pausing = FALSE;
14022         ModeHighlight();
14023         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14024             StartClocks();
14025             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14026                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14027                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14028             }
14029             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14030             HandleMachineMove(stashedInputMove, stalledEngine);
14031             stalledEngine = NULL;
14032             return;
14033         }
14034         if (gameMode == MachinePlaysWhite ||
14035             gameMode == TwoMachinesPlay   ||
14036             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14037             if(first.pause)  UnPauseEngine(&first);
14038             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14039             if(second.pause) UnPauseEngine(&second);
14040             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14041             StartClocks();
14042         } else {
14043             DisplayBothClocks();
14044         }
14045         if (gameMode == PlayFromGameFile) {
14046             if (appData.timeDelay >= 0)
14047                 AutoPlayGameLoop();
14048         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14049             Reset(FALSE, TRUE);
14050             SendToICS(ics_prefix);
14051             SendToICS("refresh\n");
14052         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14053             ForwardInner(forwardMostMove);
14054         }
14055         pauseExamInvalid = FALSE;
14056     } else {
14057         switch (gameMode) {
14058           default:
14059             return;
14060           case IcsExamining:
14061             pauseExamForwardMostMove = forwardMostMove;
14062             pauseExamInvalid = FALSE;
14063             /* fall through */
14064           case IcsObserving:
14065           case IcsPlayingWhite:
14066           case IcsPlayingBlack:
14067             pausing = TRUE;
14068             ModeHighlight();
14069             return;
14070           case PlayFromGameFile:
14071             (void) StopLoadGameTimer();
14072             pausing = TRUE;
14073             ModeHighlight();
14074             break;
14075           case BeginningOfGame:
14076             if (appData.icsActive) return;
14077             /* else fall through */
14078           case MachinePlaysWhite:
14079           case MachinePlaysBlack:
14080           case TwoMachinesPlay:
14081             if (forwardMostMove == 0)
14082               return;           /* don't pause if no one has moved */
14083             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14084                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14085                 if(onMove->pause) {           // thinking engine can be paused
14086                     PauseEngine(onMove);      // do it
14087                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14088                         PauseEngine(onMove->other);
14089                     else
14090                         SendToProgram("easy\n", onMove->other);
14091                     StopClocks();
14092                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14093             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14094                 if(first.pause) {
14095                     PauseEngine(&first);
14096                     StopClocks();
14097                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14098             } else { // human on move, pause pondering by either method
14099                 if(first.pause)
14100                     PauseEngine(&first);
14101                 else if(appData.ponderNextMove)
14102                     SendToProgram("easy\n", &first);
14103                 StopClocks();
14104             }
14105             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14106           case AnalyzeMode:
14107             pausing = TRUE;
14108             ModeHighlight();
14109             break;
14110         }
14111     }
14112 }
14113
14114 void
14115 EditCommentEvent ()
14116 {
14117     char title[MSG_SIZ];
14118
14119     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14120       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14121     } else {
14122       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14123                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14124                parseList[currentMove - 1]);
14125     }
14126
14127     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14128 }
14129
14130
14131 void
14132 EditTagsEvent ()
14133 {
14134     char *tags = PGNTags(&gameInfo);
14135     bookUp = FALSE;
14136     EditTagsPopUp(tags, NULL);
14137     free(tags);
14138 }
14139
14140 void
14141 ToggleSecond ()
14142 {
14143   if(second.analyzing) {
14144     SendToProgram("exit\n", &second);
14145     second.analyzing = FALSE;
14146   } else {
14147     if (second.pr == NoProc) StartChessProgram(&second);
14148     InitChessProgram(&second, FALSE);
14149     FeedMovesToProgram(&second, currentMove);
14150
14151     SendToProgram("analyze\n", &second);
14152     second.analyzing = TRUE;
14153   }
14154 }
14155
14156 /* Toggle ShowThinking */
14157 void
14158 ToggleShowThinking()
14159 {
14160   appData.showThinking = !appData.showThinking;
14161   ShowThinkingEvent();
14162 }
14163
14164 int
14165 AnalyzeModeEvent ()
14166 {
14167     char buf[MSG_SIZ];
14168
14169     if (!first.analysisSupport) {
14170       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14171       DisplayError(buf, 0);
14172       return 0;
14173     }
14174     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14175     if (appData.icsActive) {
14176         if (gameMode != IcsObserving) {
14177           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14178             DisplayError(buf, 0);
14179             /* secure check */
14180             if (appData.icsEngineAnalyze) {
14181                 if (appData.debugMode)
14182                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14183                 ExitAnalyzeMode();
14184                 ModeHighlight();
14185             }
14186             return 0;
14187         }
14188         /* if enable, user wants to disable icsEngineAnalyze */
14189         if (appData.icsEngineAnalyze) {
14190                 ExitAnalyzeMode();
14191                 ModeHighlight();
14192                 return 0;
14193         }
14194         appData.icsEngineAnalyze = TRUE;
14195         if (appData.debugMode)
14196             fprintf(debugFP, "ICS engine analyze starting... \n");
14197     }
14198
14199     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14200     if (appData.noChessProgram || gameMode == AnalyzeMode)
14201       return 0;
14202
14203     if (gameMode != AnalyzeFile) {
14204         if (!appData.icsEngineAnalyze) {
14205                EditGameEvent();
14206                if (gameMode != EditGame) return 0;
14207         }
14208         if (!appData.showThinking) ToggleShowThinking();
14209         ResurrectChessProgram();
14210         SendToProgram("analyze\n", &first);
14211         first.analyzing = TRUE;
14212         /*first.maybeThinking = TRUE;*/
14213         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14214         EngineOutputPopUp();
14215     }
14216     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
14217     pausing = FALSE;
14218     ModeHighlight();
14219     SetGameInfo();
14220
14221     StartAnalysisClock();
14222     GetTimeMark(&lastNodeCountTime);
14223     lastNodeCount = 0;
14224     return 1;
14225 }
14226
14227 void
14228 AnalyzeFileEvent ()
14229 {
14230     if (appData.noChessProgram || gameMode == AnalyzeFile)
14231       return;
14232
14233     if (!first.analysisSupport) {
14234       char buf[MSG_SIZ];
14235       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14236       DisplayError(buf, 0);
14237       return;
14238     }
14239
14240     if (gameMode != AnalyzeMode) {
14241         keepInfo = 1; // mere annotating should not alter PGN tags
14242         EditGameEvent();
14243         keepInfo = 0;
14244         if (gameMode != EditGame) return;
14245         if (!appData.showThinking) ToggleShowThinking();
14246         ResurrectChessProgram();
14247         SendToProgram("analyze\n", &first);
14248         first.analyzing = TRUE;
14249         /*first.maybeThinking = TRUE;*/
14250         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14251         EngineOutputPopUp();
14252     }
14253     gameMode = AnalyzeFile;
14254     pausing = FALSE;
14255     ModeHighlight();
14256
14257     StartAnalysisClock();
14258     GetTimeMark(&lastNodeCountTime);
14259     lastNodeCount = 0;
14260     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14261     AnalysisPeriodicEvent(1);
14262 }
14263
14264 void
14265 MachineWhiteEvent ()
14266 {
14267     char buf[MSG_SIZ];
14268     char *bookHit = NULL;
14269
14270     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14271       return;
14272
14273
14274     if (gameMode == PlayFromGameFile ||
14275         gameMode == TwoMachinesPlay  ||
14276         gameMode == Training         ||
14277         gameMode == AnalyzeMode      ||
14278         gameMode == EndOfGame)
14279         EditGameEvent();
14280
14281     if (gameMode == EditPosition)
14282         EditPositionDone(TRUE);
14283
14284     if (!WhiteOnMove(currentMove)) {
14285         DisplayError(_("It is not White's turn"), 0);
14286         return;
14287     }
14288
14289     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14290       ExitAnalyzeMode();
14291
14292     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14293         gameMode == AnalyzeFile)
14294         TruncateGame();
14295
14296     ResurrectChessProgram();    /* in case it isn't running */
14297     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14298         gameMode = MachinePlaysWhite;
14299         ResetClocks();
14300     } else
14301     gameMode = MachinePlaysWhite;
14302     pausing = FALSE;
14303     ModeHighlight();
14304     SetGameInfo();
14305     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14306     DisplayTitle(buf);
14307     if (first.sendName) {
14308       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14309       SendToProgram(buf, &first);
14310     }
14311     if (first.sendTime) {
14312       if (first.useColors) {
14313         SendToProgram("black\n", &first); /*gnu kludge*/
14314       }
14315       SendTimeRemaining(&first, TRUE);
14316     }
14317     if (first.useColors) {
14318       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14319     }
14320     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14321     SetMachineThinkingEnables();
14322     first.maybeThinking = TRUE;
14323     StartClocks();
14324     firstMove = FALSE;
14325
14326     if (appData.autoFlipView && !flipView) {
14327       flipView = !flipView;
14328       DrawPosition(FALSE, NULL);
14329       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14330     }
14331
14332     if(bookHit) { // [HGM] book: simulate book reply
14333         static char bookMove[MSG_SIZ]; // a bit generous?
14334
14335         programStats.nodes = programStats.depth = programStats.time =
14336         programStats.score = programStats.got_only_move = 0;
14337         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14338
14339         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14340         strcat(bookMove, bookHit);
14341         HandleMachineMove(bookMove, &first);
14342     }
14343 }
14344
14345 void
14346 MachineBlackEvent ()
14347 {
14348   char buf[MSG_SIZ];
14349   char *bookHit = NULL;
14350
14351     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14352         return;
14353
14354
14355     if (gameMode == PlayFromGameFile ||
14356         gameMode == TwoMachinesPlay  ||
14357         gameMode == Training         ||
14358         gameMode == AnalyzeMode      ||
14359         gameMode == EndOfGame)
14360         EditGameEvent();
14361
14362     if (gameMode == EditPosition)
14363         EditPositionDone(TRUE);
14364
14365     if (WhiteOnMove(currentMove)) {
14366         DisplayError(_("It is not Black's turn"), 0);
14367         return;
14368     }
14369
14370     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14371       ExitAnalyzeMode();
14372
14373     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14374         gameMode == AnalyzeFile)
14375         TruncateGame();
14376
14377     ResurrectChessProgram();    /* in case it isn't running */
14378     gameMode = MachinePlaysBlack;
14379     pausing = FALSE;
14380     ModeHighlight();
14381     SetGameInfo();
14382     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14383     DisplayTitle(buf);
14384     if (first.sendName) {
14385       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14386       SendToProgram(buf, &first);
14387     }
14388     if (first.sendTime) {
14389       if (first.useColors) {
14390         SendToProgram("white\n", &first); /*gnu kludge*/
14391       }
14392       SendTimeRemaining(&first, FALSE);
14393     }
14394     if (first.useColors) {
14395       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14396     }
14397     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14398     SetMachineThinkingEnables();
14399     first.maybeThinking = TRUE;
14400     StartClocks();
14401
14402     if (appData.autoFlipView && flipView) {
14403       flipView = !flipView;
14404       DrawPosition(FALSE, NULL);
14405       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14406     }
14407     if(bookHit) { // [HGM] book: simulate book reply
14408         static char bookMove[MSG_SIZ]; // a bit generous?
14409
14410         programStats.nodes = programStats.depth = programStats.time =
14411         programStats.score = programStats.got_only_move = 0;
14412         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14413
14414         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14415         strcat(bookMove, bookHit);
14416         HandleMachineMove(bookMove, &first);
14417     }
14418 }
14419
14420
14421 void
14422 DisplayTwoMachinesTitle ()
14423 {
14424     char buf[MSG_SIZ];
14425     if (appData.matchGames > 0) {
14426         if(appData.tourneyFile[0]) {
14427           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14428                    gameInfo.white, _("vs."), gameInfo.black,
14429                    nextGame+1, appData.matchGames+1,
14430                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14431         } else
14432         if (first.twoMachinesColor[0] == 'w') {
14433           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14434                    gameInfo.white, _("vs."),  gameInfo.black,
14435                    first.matchWins, second.matchWins,
14436                    matchGame - 1 - (first.matchWins + second.matchWins));
14437         } else {
14438           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14439                    gameInfo.white, _("vs."), gameInfo.black,
14440                    second.matchWins, first.matchWins,
14441                    matchGame - 1 - (first.matchWins + second.matchWins));
14442         }
14443     } else {
14444       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14445     }
14446     DisplayTitle(buf);
14447 }
14448
14449 void
14450 SettingsMenuIfReady ()
14451 {
14452   if (second.lastPing != second.lastPong) {
14453     DisplayMessage("", _("Waiting for second chess program"));
14454     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14455     return;
14456   }
14457   ThawUI();
14458   DisplayMessage("", "");
14459   SettingsPopUp(&second);
14460 }
14461
14462 int
14463 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14464 {
14465     char buf[MSG_SIZ];
14466     if (cps->pr == NoProc) {
14467         StartChessProgram(cps);
14468         if (cps->protocolVersion == 1) {
14469           retry();
14470           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14471         } else {
14472           /* kludge: allow timeout for initial "feature" command */
14473           if(retry != TwoMachinesEventIfReady) FreezeUI();
14474           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14475           DisplayMessage("", buf);
14476           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14477         }
14478         return 1;
14479     }
14480     return 0;
14481 }
14482
14483 void
14484 TwoMachinesEvent P((void))
14485 {
14486     int i;
14487     char buf[MSG_SIZ];
14488     ChessProgramState *onmove;
14489     char *bookHit = NULL;
14490     static int stalling = 0;
14491     TimeMark now;
14492     long wait;
14493
14494     if (appData.noChessProgram) return;
14495
14496     switch (gameMode) {
14497       case TwoMachinesPlay:
14498         return;
14499       case MachinePlaysWhite:
14500       case MachinePlaysBlack:
14501         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14502             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14503             return;
14504         }
14505         /* fall through */
14506       case BeginningOfGame:
14507       case PlayFromGameFile:
14508       case EndOfGame:
14509         EditGameEvent();
14510         if (gameMode != EditGame) return;
14511         break;
14512       case EditPosition:
14513         EditPositionDone(TRUE);
14514         break;
14515       case AnalyzeMode:
14516       case AnalyzeFile:
14517         ExitAnalyzeMode();
14518         break;
14519       case EditGame:
14520       default:
14521         break;
14522     }
14523
14524 //    forwardMostMove = currentMove;
14525     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14526     startingEngine = TRUE;
14527
14528     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14529
14530     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14531     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14532       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14533       return;
14534     }
14535     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14536
14537     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14538                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14539         startingEngine = FALSE;
14540         DisplayError("second engine does not play this", 0);
14541         return;
14542     }
14543
14544     if(!stalling) {
14545       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14546       SendToProgram("force\n", &second);
14547       stalling = 1;
14548       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14549       return;
14550     }
14551     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14552     if(appData.matchPause>10000 || appData.matchPause<10)
14553                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14554     wait = SubtractTimeMarks(&now, &pauseStart);
14555     if(wait < appData.matchPause) {
14556         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14557         return;
14558     }
14559     // we are now committed to starting the game
14560     stalling = 0;
14561     DisplayMessage("", "");
14562     if (startedFromSetupPosition) {
14563         SendBoard(&second, backwardMostMove);
14564     if (appData.debugMode) {
14565         fprintf(debugFP, "Two Machines\n");
14566     }
14567     }
14568     for (i = backwardMostMove; i < forwardMostMove; i++) {
14569         SendMoveToProgram(i, &second);
14570     }
14571
14572     gameMode = TwoMachinesPlay;
14573     pausing = startingEngine = FALSE;
14574     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14575     SetGameInfo();
14576     DisplayTwoMachinesTitle();
14577     firstMove = TRUE;
14578     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14579         onmove = &first;
14580     } else {
14581         onmove = &second;
14582     }
14583     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14584     SendToProgram(first.computerString, &first);
14585     if (first.sendName) {
14586       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14587       SendToProgram(buf, &first);
14588     }
14589     SendToProgram(second.computerString, &second);
14590     if (second.sendName) {
14591       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14592       SendToProgram(buf, &second);
14593     }
14594
14595     ResetClocks();
14596     if (!first.sendTime || !second.sendTime) {
14597         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14598         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14599     }
14600     if (onmove->sendTime) {
14601       if (onmove->useColors) {
14602         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14603       }
14604       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14605     }
14606     if (onmove->useColors) {
14607       SendToProgram(onmove->twoMachinesColor, onmove);
14608     }
14609     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14610 //    SendToProgram("go\n", onmove);
14611     onmove->maybeThinking = TRUE;
14612     SetMachineThinkingEnables();
14613
14614     StartClocks();
14615
14616     if(bookHit) { // [HGM] book: simulate book reply
14617         static char bookMove[MSG_SIZ]; // a bit generous?
14618
14619         programStats.nodes = programStats.depth = programStats.time =
14620         programStats.score = programStats.got_only_move = 0;
14621         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14622
14623         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14624         strcat(bookMove, bookHit);
14625         savedMessage = bookMove; // args for deferred call
14626         savedState = onmove;
14627         ScheduleDelayedEvent(DeferredBookMove, 1);
14628     }
14629 }
14630
14631 void
14632 TrainingEvent ()
14633 {
14634     if (gameMode == Training) {
14635       SetTrainingModeOff();
14636       gameMode = PlayFromGameFile;
14637       DisplayMessage("", _("Training mode off"));
14638     } else {
14639       gameMode = Training;
14640       animateTraining = appData.animate;
14641
14642       /* make sure we are not already at the end of the game */
14643       if (currentMove < forwardMostMove) {
14644         SetTrainingModeOn();
14645         DisplayMessage("", _("Training mode on"));
14646       } else {
14647         gameMode = PlayFromGameFile;
14648         DisplayError(_("Already at end of game"), 0);
14649       }
14650     }
14651     ModeHighlight();
14652 }
14653
14654 void
14655 IcsClientEvent ()
14656 {
14657     if (!appData.icsActive) return;
14658     switch (gameMode) {
14659       case IcsPlayingWhite:
14660       case IcsPlayingBlack:
14661       case IcsObserving:
14662       case IcsIdle:
14663       case BeginningOfGame:
14664       case IcsExamining:
14665         return;
14666
14667       case EditGame:
14668         break;
14669
14670       case EditPosition:
14671         EditPositionDone(TRUE);
14672         break;
14673
14674       case AnalyzeMode:
14675       case AnalyzeFile:
14676         ExitAnalyzeMode();
14677         break;
14678
14679       default:
14680         EditGameEvent();
14681         break;
14682     }
14683
14684     gameMode = IcsIdle;
14685     ModeHighlight();
14686     return;
14687 }
14688
14689 void
14690 EditGameEvent ()
14691 {
14692     int i;
14693
14694     switch (gameMode) {
14695       case Training:
14696         SetTrainingModeOff();
14697         break;
14698       case MachinePlaysWhite:
14699       case MachinePlaysBlack:
14700       case BeginningOfGame:
14701         SendToProgram("force\n", &first);
14702         SetUserThinkingEnables();
14703         break;
14704       case PlayFromGameFile:
14705         (void) StopLoadGameTimer();
14706         if (gameFileFP != NULL) {
14707             gameFileFP = NULL;
14708         }
14709         break;
14710       case EditPosition:
14711         EditPositionDone(TRUE);
14712         break;
14713       case AnalyzeMode:
14714       case AnalyzeFile:
14715         ExitAnalyzeMode();
14716         SendToProgram("force\n", &first);
14717         break;
14718       case TwoMachinesPlay:
14719         GameEnds(EndOfFile, NULL, GE_PLAYER);
14720         ResurrectChessProgram();
14721         SetUserThinkingEnables();
14722         break;
14723       case EndOfGame:
14724         ResurrectChessProgram();
14725         break;
14726       case IcsPlayingBlack:
14727       case IcsPlayingWhite:
14728         DisplayError(_("Warning: You are still playing a game"), 0);
14729         break;
14730       case IcsObserving:
14731         DisplayError(_("Warning: You are still observing a game"), 0);
14732         break;
14733       case IcsExamining:
14734         DisplayError(_("Warning: You are still examining a game"), 0);
14735         break;
14736       case IcsIdle:
14737         break;
14738       case EditGame:
14739       default:
14740         return;
14741     }
14742
14743     pausing = FALSE;
14744     StopClocks();
14745     first.offeredDraw = second.offeredDraw = 0;
14746
14747     if (gameMode == PlayFromGameFile) {
14748         whiteTimeRemaining = timeRemaining[0][currentMove];
14749         blackTimeRemaining = timeRemaining[1][currentMove];
14750         DisplayTitle("");
14751     }
14752
14753     if (gameMode == MachinePlaysWhite ||
14754         gameMode == MachinePlaysBlack ||
14755         gameMode == TwoMachinesPlay ||
14756         gameMode == EndOfGame) {
14757         i = forwardMostMove;
14758         while (i > currentMove) {
14759             SendToProgram("undo\n", &first);
14760             i--;
14761         }
14762         if(!adjustedClock) {
14763         whiteTimeRemaining = timeRemaining[0][currentMove];
14764         blackTimeRemaining = timeRemaining[1][currentMove];
14765         DisplayBothClocks();
14766         }
14767         if (whiteFlag || blackFlag) {
14768             whiteFlag = blackFlag = 0;
14769         }
14770         DisplayTitle("");
14771     }
14772
14773     gameMode = EditGame;
14774     ModeHighlight();
14775     SetGameInfo();
14776 }
14777
14778
14779 void
14780 EditPositionEvent ()
14781 {
14782     if (gameMode == EditPosition) {
14783         EditGameEvent();
14784         return;
14785     }
14786
14787     EditGameEvent();
14788     if (gameMode != EditGame) return;
14789
14790     gameMode = EditPosition;
14791     ModeHighlight();
14792     SetGameInfo();
14793     if (currentMove > 0)
14794       CopyBoard(boards[0], boards[currentMove]);
14795
14796     blackPlaysFirst = !WhiteOnMove(currentMove);
14797     ResetClocks();
14798     currentMove = forwardMostMove = backwardMostMove = 0;
14799     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14800     DisplayMove(-1);
14801     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14802 }
14803
14804 void
14805 ExitAnalyzeMode ()
14806 {
14807     /* [DM] icsEngineAnalyze - possible call from other functions */
14808     if (appData.icsEngineAnalyze) {
14809         appData.icsEngineAnalyze = FALSE;
14810
14811         DisplayMessage("",_("Close ICS engine analyze..."));
14812     }
14813     if (first.analysisSupport && first.analyzing) {
14814       SendToBoth("exit\n");
14815       first.analyzing = second.analyzing = FALSE;
14816     }
14817     thinkOutput[0] = NULLCHAR;
14818 }
14819
14820 void
14821 EditPositionDone (Boolean fakeRights)
14822 {
14823     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14824
14825     startedFromSetupPosition = TRUE;
14826     InitChessProgram(&first, FALSE);
14827     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14828       boards[0][EP_STATUS] = EP_NONE;
14829       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14830       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14831         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14832         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14833       } else boards[0][CASTLING][2] = NoRights;
14834       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14835         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14836         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14837       } else boards[0][CASTLING][5] = NoRights;
14838       if(gameInfo.variant == VariantSChess) {
14839         int i;
14840         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14841           boards[0][VIRGIN][i] = 0;
14842           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14843           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
14844         }
14845       }
14846     }
14847     SendToProgram("force\n", &first);
14848     if (blackPlaysFirst) {
14849         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
14850         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
14851         currentMove = forwardMostMove = backwardMostMove = 1;
14852         CopyBoard(boards[1], boards[0]);
14853     } else {
14854         currentMove = forwardMostMove = backwardMostMove = 0;
14855     }
14856     SendBoard(&first, forwardMostMove);
14857     if (appData.debugMode) {
14858         fprintf(debugFP, "EditPosDone\n");
14859     }
14860     DisplayTitle("");
14861     DisplayMessage("", "");
14862     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14863     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14864     gameMode = EditGame;
14865     ModeHighlight();
14866     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14867     ClearHighlights(); /* [AS] */
14868 }
14869
14870 /* Pause for `ms' milliseconds */
14871 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14872 void
14873 TimeDelay (long ms)
14874 {
14875     TimeMark m1, m2;
14876
14877     GetTimeMark(&m1);
14878     do {
14879         GetTimeMark(&m2);
14880     } while (SubtractTimeMarks(&m2, &m1) < ms);
14881 }
14882
14883 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
14884 void
14885 SendMultiLineToICS (char *buf)
14886 {
14887     char temp[MSG_SIZ+1], *p;
14888     int len;
14889
14890     len = strlen(buf);
14891     if (len > MSG_SIZ)
14892       len = MSG_SIZ;
14893
14894     strncpy(temp, buf, len);
14895     temp[len] = 0;
14896
14897     p = temp;
14898     while (*p) {
14899         if (*p == '\n' || *p == '\r')
14900           *p = ' ';
14901         ++p;
14902     }
14903
14904     strcat(temp, "\n");
14905     SendToICS(temp);
14906     SendToPlayer(temp, strlen(temp));
14907 }
14908
14909 void
14910 SetWhiteToPlayEvent ()
14911 {
14912     if (gameMode == EditPosition) {
14913         blackPlaysFirst = FALSE;
14914         DisplayBothClocks();    /* works because currentMove is 0 */
14915     } else if (gameMode == IcsExamining) {
14916         SendToICS(ics_prefix);
14917         SendToICS("tomove white\n");
14918     }
14919 }
14920
14921 void
14922 SetBlackToPlayEvent ()
14923 {
14924     if (gameMode == EditPosition) {
14925         blackPlaysFirst = TRUE;
14926         currentMove = 1;        /* kludge */
14927         DisplayBothClocks();
14928         currentMove = 0;
14929     } else if (gameMode == IcsExamining) {
14930         SendToICS(ics_prefix);
14931         SendToICS("tomove black\n");
14932     }
14933 }
14934
14935 void
14936 EditPositionMenuEvent (ChessSquare selection, int x, int y)
14937 {
14938     char buf[MSG_SIZ];
14939     ChessSquare piece = boards[0][y][x];
14940     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
14941     static int lastVariant;
14942
14943     if (gameMode != EditPosition && gameMode != IcsExamining) return;
14944
14945     switch (selection) {
14946       case ClearBoard:
14947         CopyBoard(currentBoard, boards[0]);
14948         CopyBoard(menuBoard, initialPosition);
14949         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
14950             SendToICS(ics_prefix);
14951             SendToICS("bsetup clear\n");
14952         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
14953             SendToICS(ics_prefix);
14954             SendToICS("clearboard\n");
14955         } else {
14956             int nonEmpty = 0;
14957             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14958                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14959                 for (y = 0; y < BOARD_HEIGHT; y++) {
14960                     if (gameMode == IcsExamining) {
14961                         if (boards[currentMove][y][x] != EmptySquare) {
14962                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14963                                     AAA + x, ONE + y);
14964                             SendToICS(buf);
14965                         }
14966                     } else {
14967                         if(boards[0][y][x] != p) nonEmpty++;
14968                         boards[0][y][x] = p;
14969                     }
14970                 }
14971                 menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
14972             }
14973             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
14974                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
14975                     ChessSquare p = menuBoard[0][x];
14976                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
14977                     p = menuBoard[BOARD_HEIGHT-1][x];
14978                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
14979                 }
14980                 DisplayMessage("Clicking clock again restores position", "");
14981                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
14982                 if(!nonEmpty) { // asked to clear an empty board
14983                     CopyBoard(boards[0], menuBoard);
14984                 } else
14985                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
14986                     CopyBoard(boards[0], initialPosition);
14987                 } else
14988                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
14989                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
14990                     CopyBoard(boards[0], erasedBoard);
14991                 } else
14992                     CopyBoard(erasedBoard, currentBoard);
14993
14994             }
14995         }
14996         if (gameMode == EditPosition) {
14997             DrawPosition(FALSE, boards[0]);
14998         }
14999         break;
15000
15001       case WhitePlay:
15002         SetWhiteToPlayEvent();
15003         break;
15004
15005       case BlackPlay:
15006         SetBlackToPlayEvent();
15007         break;
15008
15009       case EmptySquare:
15010         if (gameMode == IcsExamining) {
15011             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15012             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15013             SendToICS(buf);
15014         } else {
15015             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15016                 if(x == BOARD_LEFT-2) {
15017                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15018                     boards[0][y][1] = 0;
15019                 } else
15020                 if(x == BOARD_RGHT+1) {
15021                     if(y >= gameInfo.holdingsSize) break;
15022                     boards[0][y][BOARD_WIDTH-2] = 0;
15023                 } else break;
15024             }
15025             boards[0][y][x] = EmptySquare;
15026             DrawPosition(FALSE, boards[0]);
15027         }
15028         break;
15029
15030       case PromotePiece:
15031         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15032            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15033             selection = (ChessSquare) (PROMOTED piece);
15034         } else if(piece == EmptySquare) selection = WhiteSilver;
15035         else selection = (ChessSquare)((int)piece - 1);
15036         goto defaultlabel;
15037
15038       case DemotePiece:
15039         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15040            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15041             selection = (ChessSquare) (DEMOTED piece);
15042         } else if(piece == EmptySquare) selection = BlackSilver;
15043         else selection = (ChessSquare)((int)piece + 1);
15044         goto defaultlabel;
15045
15046       case WhiteQueen:
15047       case BlackQueen:
15048         if(gameInfo.variant == VariantShatranj ||
15049            gameInfo.variant == VariantXiangqi  ||
15050            gameInfo.variant == VariantCourier  ||
15051            gameInfo.variant == VariantASEAN    ||
15052            gameInfo.variant == VariantMakruk     )
15053             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15054         goto defaultlabel;
15055
15056       case WhiteKing:
15057       case BlackKing:
15058         if(gameInfo.variant == VariantXiangqi)
15059             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15060         if(gameInfo.variant == VariantKnightmate)
15061             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15062       default:
15063         defaultlabel:
15064         if (gameMode == IcsExamining) {
15065             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15066             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15067                      PieceToChar(selection), AAA + x, ONE + y);
15068             SendToICS(buf);
15069         } else {
15070             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15071                 int n;
15072                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15073                     n = PieceToNumber(selection - BlackPawn);
15074                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15075                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15076                     boards[0][BOARD_HEIGHT-1-n][1]++;
15077                 } else
15078                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15079                     n = PieceToNumber(selection);
15080                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15081                     boards[0][n][BOARD_WIDTH-1] = selection;
15082                     boards[0][n][BOARD_WIDTH-2]++;
15083                 }
15084             } else
15085             boards[0][y][x] = selection;
15086             DrawPosition(TRUE, boards[0]);
15087             ClearHighlights();
15088             fromX = fromY = -1;
15089         }
15090         break;
15091     }
15092 }
15093
15094
15095 void
15096 DropMenuEvent (ChessSquare selection, int x, int y)
15097 {
15098     ChessMove moveType;
15099
15100     switch (gameMode) {
15101       case IcsPlayingWhite:
15102       case MachinePlaysBlack:
15103         if (!WhiteOnMove(currentMove)) {
15104             DisplayMoveError(_("It is Black's turn"));
15105             return;
15106         }
15107         moveType = WhiteDrop;
15108         break;
15109       case IcsPlayingBlack:
15110       case MachinePlaysWhite:
15111         if (WhiteOnMove(currentMove)) {
15112             DisplayMoveError(_("It is White's turn"));
15113             return;
15114         }
15115         moveType = BlackDrop;
15116         break;
15117       case EditGame:
15118         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15119         break;
15120       default:
15121         return;
15122     }
15123
15124     if (moveType == BlackDrop && selection < BlackPawn) {
15125       selection = (ChessSquare) ((int) selection
15126                                  + (int) BlackPawn - (int) WhitePawn);
15127     }
15128     if (boards[currentMove][y][x] != EmptySquare) {
15129         DisplayMoveError(_("That square is occupied"));
15130         return;
15131     }
15132
15133     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15134 }
15135
15136 void
15137 AcceptEvent ()
15138 {
15139     /* Accept a pending offer of any kind from opponent */
15140
15141     if (appData.icsActive) {
15142         SendToICS(ics_prefix);
15143         SendToICS("accept\n");
15144     } else if (cmailMsgLoaded) {
15145         if (currentMove == cmailOldMove &&
15146             commentList[cmailOldMove] != NULL &&
15147             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15148                    "Black offers a draw" : "White offers a draw")) {
15149             TruncateGame();
15150             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15151             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15152         } else {
15153             DisplayError(_("There is no pending offer on this move"), 0);
15154             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15155         }
15156     } else {
15157         /* Not used for offers from chess program */
15158     }
15159 }
15160
15161 void
15162 DeclineEvent ()
15163 {
15164     /* Decline a pending offer of any kind from opponent */
15165
15166     if (appData.icsActive) {
15167         SendToICS(ics_prefix);
15168         SendToICS("decline\n");
15169     } else if (cmailMsgLoaded) {
15170         if (currentMove == cmailOldMove &&
15171             commentList[cmailOldMove] != NULL &&
15172             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15173                    "Black offers a draw" : "White offers a draw")) {
15174 #ifdef NOTDEF
15175             AppendComment(cmailOldMove, "Draw declined", TRUE);
15176             DisplayComment(cmailOldMove - 1, "Draw declined");
15177 #endif /*NOTDEF*/
15178         } else {
15179             DisplayError(_("There is no pending offer on this move"), 0);
15180         }
15181     } else {
15182         /* Not used for offers from chess program */
15183     }
15184 }
15185
15186 void
15187 RematchEvent ()
15188 {
15189     /* Issue ICS rematch command */
15190     if (appData.icsActive) {
15191         SendToICS(ics_prefix);
15192         SendToICS("rematch\n");
15193     }
15194 }
15195
15196 void
15197 CallFlagEvent ()
15198 {
15199     /* Call your opponent's flag (claim a win on time) */
15200     if (appData.icsActive) {
15201         SendToICS(ics_prefix);
15202         SendToICS("flag\n");
15203     } else {
15204         switch (gameMode) {
15205           default:
15206             return;
15207           case MachinePlaysWhite:
15208             if (whiteFlag) {
15209                 if (blackFlag)
15210                   GameEnds(GameIsDrawn, "Both players ran out of time",
15211                            GE_PLAYER);
15212                 else
15213                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15214             } else {
15215                 DisplayError(_("Your opponent is not out of time"), 0);
15216             }
15217             break;
15218           case MachinePlaysBlack:
15219             if (blackFlag) {
15220                 if (whiteFlag)
15221                   GameEnds(GameIsDrawn, "Both players ran out of time",
15222                            GE_PLAYER);
15223                 else
15224                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15225             } else {
15226                 DisplayError(_("Your opponent is not out of time"), 0);
15227             }
15228             break;
15229         }
15230     }
15231 }
15232
15233 void
15234 ClockClick (int which)
15235 {       // [HGM] code moved to back-end from winboard.c
15236         if(which) { // black clock
15237           if (gameMode == EditPosition || gameMode == IcsExamining) {
15238             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15239             SetBlackToPlayEvent();
15240           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
15241           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15242           } else if (shiftKey) {
15243             AdjustClock(which, -1);
15244           } else if (gameMode == IcsPlayingWhite ||
15245                      gameMode == MachinePlaysBlack) {
15246             CallFlagEvent();
15247           }
15248         } else { // white clock
15249           if (gameMode == EditPosition || gameMode == IcsExamining) {
15250             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15251             SetWhiteToPlayEvent();
15252           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
15253           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15254           } else if (shiftKey) {
15255             AdjustClock(which, -1);
15256           } else if (gameMode == IcsPlayingBlack ||
15257                    gameMode == MachinePlaysWhite) {
15258             CallFlagEvent();
15259           }
15260         }
15261 }
15262
15263 void
15264 DrawEvent ()
15265 {
15266     /* Offer draw or accept pending draw offer from opponent */
15267
15268     if (appData.icsActive) {
15269         /* Note: tournament rules require draw offers to be
15270            made after you make your move but before you punch
15271            your clock.  Currently ICS doesn't let you do that;
15272            instead, you immediately punch your clock after making
15273            a move, but you can offer a draw at any time. */
15274
15275         SendToICS(ics_prefix);
15276         SendToICS("draw\n");
15277         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15278     } else if (cmailMsgLoaded) {
15279         if (currentMove == cmailOldMove &&
15280             commentList[cmailOldMove] != NULL &&
15281             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15282                    "Black offers a draw" : "White offers a draw")) {
15283             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15284             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15285         } else if (currentMove == cmailOldMove + 1) {
15286             char *offer = WhiteOnMove(cmailOldMove) ?
15287               "White offers a draw" : "Black offers a draw";
15288             AppendComment(currentMove, offer, TRUE);
15289             DisplayComment(currentMove - 1, offer);
15290             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15291         } else {
15292             DisplayError(_("You must make your move before offering a draw"), 0);
15293             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15294         }
15295     } else if (first.offeredDraw) {
15296         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15297     } else {
15298         if (first.sendDrawOffers) {
15299             SendToProgram("draw\n", &first);
15300             userOfferedDraw = TRUE;
15301         }
15302     }
15303 }
15304
15305 void
15306 AdjournEvent ()
15307 {
15308     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15309
15310     if (appData.icsActive) {
15311         SendToICS(ics_prefix);
15312         SendToICS("adjourn\n");
15313     } else {
15314         /* Currently GNU Chess doesn't offer or accept Adjourns */
15315     }
15316 }
15317
15318
15319 void
15320 AbortEvent ()
15321 {
15322     /* Offer Abort or accept pending Abort offer from opponent */
15323
15324     if (appData.icsActive) {
15325         SendToICS(ics_prefix);
15326         SendToICS("abort\n");
15327     } else {
15328         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15329     }
15330 }
15331
15332 void
15333 ResignEvent ()
15334 {
15335     /* Resign.  You can do this even if it's not your turn. */
15336
15337     if (appData.icsActive) {
15338         SendToICS(ics_prefix);
15339         SendToICS("resign\n");
15340     } else {
15341         switch (gameMode) {
15342           case MachinePlaysWhite:
15343             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15344             break;
15345           case MachinePlaysBlack:
15346             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15347             break;
15348           case EditGame:
15349             if (cmailMsgLoaded) {
15350                 TruncateGame();
15351                 if (WhiteOnMove(cmailOldMove)) {
15352                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15353                 } else {
15354                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15355                 }
15356                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15357             }
15358             break;
15359           default:
15360             break;
15361         }
15362     }
15363 }
15364
15365
15366 void
15367 StopObservingEvent ()
15368 {
15369     /* Stop observing current games */
15370     SendToICS(ics_prefix);
15371     SendToICS("unobserve\n");
15372 }
15373
15374 void
15375 StopExaminingEvent ()
15376 {
15377     /* Stop observing current game */
15378     SendToICS(ics_prefix);
15379     SendToICS("unexamine\n");
15380 }
15381
15382 void
15383 ForwardInner (int target)
15384 {
15385     int limit; int oldSeekGraphUp = seekGraphUp;
15386
15387     if (appData.debugMode)
15388         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15389                 target, currentMove, forwardMostMove);
15390
15391     if (gameMode == EditPosition)
15392       return;
15393
15394     seekGraphUp = FALSE;
15395     MarkTargetSquares(1);
15396
15397     if (gameMode == PlayFromGameFile && !pausing)
15398       PauseEvent();
15399
15400     if (gameMode == IcsExamining && pausing)
15401       limit = pauseExamForwardMostMove;
15402     else
15403       limit = forwardMostMove;
15404
15405     if (target > limit) target = limit;
15406
15407     if (target > 0 && moveList[target - 1][0]) {
15408         int fromX, fromY, toX, toY;
15409         toX = moveList[target - 1][2] - AAA;
15410         toY = moveList[target - 1][3] - ONE;
15411         if (moveList[target - 1][1] == '@') {
15412             if (appData.highlightLastMove) {
15413                 SetHighlights(-1, -1, toX, toY);
15414             }
15415         } else {
15416             fromX = moveList[target - 1][0] - AAA;
15417             fromY = moveList[target - 1][1] - ONE;
15418             if (target == currentMove + 1) {
15419                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15420             }
15421             if (appData.highlightLastMove) {
15422                 SetHighlights(fromX, fromY, toX, toY);
15423             }
15424         }
15425     }
15426     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15427         gameMode == Training || gameMode == PlayFromGameFile ||
15428         gameMode == AnalyzeFile) {
15429         while (currentMove < target) {
15430             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15431             SendMoveToProgram(currentMove++, &first);
15432         }
15433     } else {
15434         currentMove = target;
15435     }
15436
15437     if (gameMode == EditGame || gameMode == EndOfGame) {
15438         whiteTimeRemaining = timeRemaining[0][currentMove];
15439         blackTimeRemaining = timeRemaining[1][currentMove];
15440     }
15441     DisplayBothClocks();
15442     DisplayMove(currentMove - 1);
15443     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15444     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15445     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15446         DisplayComment(currentMove - 1, commentList[currentMove]);
15447     }
15448     ClearMap(); // [HGM] exclude: invalidate map
15449 }
15450
15451
15452 void
15453 ForwardEvent ()
15454 {
15455     if (gameMode == IcsExamining && !pausing) {
15456         SendToICS(ics_prefix);
15457         SendToICS("forward\n");
15458     } else {
15459         ForwardInner(currentMove + 1);
15460     }
15461 }
15462
15463 void
15464 ToEndEvent ()
15465 {
15466     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15467         /* to optimze, we temporarily turn off analysis mode while we feed
15468          * the remaining moves to the engine. Otherwise we get analysis output
15469          * after each move.
15470          */
15471         if (first.analysisSupport) {
15472           SendToProgram("exit\nforce\n", &first);
15473           first.analyzing = FALSE;
15474         }
15475     }
15476
15477     if (gameMode == IcsExamining && !pausing) {
15478         SendToICS(ics_prefix);
15479         SendToICS("forward 999999\n");
15480     } else {
15481         ForwardInner(forwardMostMove);
15482     }
15483
15484     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15485         /* we have fed all the moves, so reactivate analysis mode */
15486         SendToProgram("analyze\n", &first);
15487         first.analyzing = TRUE;
15488         /*first.maybeThinking = TRUE;*/
15489         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15490     }
15491 }
15492
15493 void
15494 BackwardInner (int target)
15495 {
15496     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15497
15498     if (appData.debugMode)
15499         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15500                 target, currentMove, forwardMostMove);
15501
15502     if (gameMode == EditPosition) return;
15503     seekGraphUp = FALSE;
15504     MarkTargetSquares(1);
15505     if (currentMove <= backwardMostMove) {
15506         ClearHighlights();
15507         DrawPosition(full_redraw, boards[currentMove]);
15508         return;
15509     }
15510     if (gameMode == PlayFromGameFile && !pausing)
15511       PauseEvent();
15512
15513     if (moveList[target][0]) {
15514         int fromX, fromY, toX, toY;
15515         toX = moveList[target][2] - AAA;
15516         toY = moveList[target][3] - ONE;
15517         if (moveList[target][1] == '@') {
15518             if (appData.highlightLastMove) {
15519                 SetHighlights(-1, -1, toX, toY);
15520             }
15521         } else {
15522             fromX = moveList[target][0] - AAA;
15523             fromY = moveList[target][1] - ONE;
15524             if (target == currentMove - 1) {
15525                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15526             }
15527             if (appData.highlightLastMove) {
15528                 SetHighlights(fromX, fromY, toX, toY);
15529             }
15530         }
15531     }
15532     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15533         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15534         while (currentMove > target) {
15535             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15536                 // null move cannot be undone. Reload program with move history before it.
15537                 int i;
15538                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15539                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15540                 }
15541                 SendBoard(&first, i);
15542               if(second.analyzing) SendBoard(&second, i);
15543                 for(currentMove=i; currentMove<target; currentMove++) {
15544                     SendMoveToProgram(currentMove, &first);
15545                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15546                 }
15547                 break;
15548             }
15549             SendToBoth("undo\n");
15550             currentMove--;
15551         }
15552     } else {
15553         currentMove = target;
15554     }
15555
15556     if (gameMode == EditGame || gameMode == EndOfGame) {
15557         whiteTimeRemaining = timeRemaining[0][currentMove];
15558         blackTimeRemaining = timeRemaining[1][currentMove];
15559     }
15560     DisplayBothClocks();
15561     DisplayMove(currentMove - 1);
15562     DrawPosition(full_redraw, boards[currentMove]);
15563     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15564     // [HGM] PV info: routine tests if comment empty
15565     DisplayComment(currentMove - 1, commentList[currentMove]);
15566     ClearMap(); // [HGM] exclude: invalidate map
15567 }
15568
15569 void
15570 BackwardEvent ()
15571 {
15572     if (gameMode == IcsExamining && !pausing) {
15573         SendToICS(ics_prefix);
15574         SendToICS("backward\n");
15575     } else {
15576         BackwardInner(currentMove - 1);
15577     }
15578 }
15579
15580 void
15581 ToStartEvent ()
15582 {
15583     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15584         /* to optimize, we temporarily turn off analysis mode while we undo
15585          * all the moves. Otherwise we get analysis output after each undo.
15586          */
15587         if (first.analysisSupport) {
15588           SendToProgram("exit\nforce\n", &first);
15589           first.analyzing = FALSE;
15590         }
15591     }
15592
15593     if (gameMode == IcsExamining && !pausing) {
15594         SendToICS(ics_prefix);
15595         SendToICS("backward 999999\n");
15596     } else {
15597         BackwardInner(backwardMostMove);
15598     }
15599
15600     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15601         /* we have fed all the moves, so reactivate analysis mode */
15602         SendToProgram("analyze\n", &first);
15603         first.analyzing = TRUE;
15604         /*first.maybeThinking = TRUE;*/
15605         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15606     }
15607 }
15608
15609 void
15610 ToNrEvent (int to)
15611 {
15612   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15613   if (to >= forwardMostMove) to = forwardMostMove;
15614   if (to <= backwardMostMove) to = backwardMostMove;
15615   if (to < currentMove) {
15616     BackwardInner(to);
15617   } else {
15618     ForwardInner(to);
15619   }
15620 }
15621
15622 void
15623 RevertEvent (Boolean annotate)
15624 {
15625     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15626         return;
15627     }
15628     if (gameMode != IcsExamining) {
15629         DisplayError(_("You are not examining a game"), 0);
15630         return;
15631     }
15632     if (pausing) {
15633         DisplayError(_("You can't revert while pausing"), 0);
15634         return;
15635     }
15636     SendToICS(ics_prefix);
15637     SendToICS("revert\n");
15638 }
15639
15640 void
15641 RetractMoveEvent ()
15642 {
15643     switch (gameMode) {
15644       case MachinePlaysWhite:
15645       case MachinePlaysBlack:
15646         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15647             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15648             return;
15649         }
15650         if (forwardMostMove < 2) return;
15651         currentMove = forwardMostMove = forwardMostMove - 2;
15652         whiteTimeRemaining = timeRemaining[0][currentMove];
15653         blackTimeRemaining = timeRemaining[1][currentMove];
15654         DisplayBothClocks();
15655         DisplayMove(currentMove - 1);
15656         ClearHighlights();/*!! could figure this out*/
15657         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15658         SendToProgram("remove\n", &first);
15659         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15660         break;
15661
15662       case BeginningOfGame:
15663       default:
15664         break;
15665
15666       case IcsPlayingWhite:
15667       case IcsPlayingBlack:
15668         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15669             SendToICS(ics_prefix);
15670             SendToICS("takeback 2\n");
15671         } else {
15672             SendToICS(ics_prefix);
15673             SendToICS("takeback 1\n");
15674         }
15675         break;
15676     }
15677 }
15678
15679 void
15680 MoveNowEvent ()
15681 {
15682     ChessProgramState *cps;
15683
15684     switch (gameMode) {
15685       case MachinePlaysWhite:
15686         if (!WhiteOnMove(forwardMostMove)) {
15687             DisplayError(_("It is your turn"), 0);
15688             return;
15689         }
15690         cps = &first;
15691         break;
15692       case MachinePlaysBlack:
15693         if (WhiteOnMove(forwardMostMove)) {
15694             DisplayError(_("It is your turn"), 0);
15695             return;
15696         }
15697         cps = &first;
15698         break;
15699       case TwoMachinesPlay:
15700         if (WhiteOnMove(forwardMostMove) ==
15701             (first.twoMachinesColor[0] == 'w')) {
15702             cps = &first;
15703         } else {
15704             cps = &second;
15705         }
15706         break;
15707       case BeginningOfGame:
15708       default:
15709         return;
15710     }
15711     SendToProgram("?\n", cps);
15712 }
15713
15714 void
15715 TruncateGameEvent ()
15716 {
15717     EditGameEvent();
15718     if (gameMode != EditGame) return;
15719     TruncateGame();
15720 }
15721
15722 void
15723 TruncateGame ()
15724 {
15725     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15726     if (forwardMostMove > currentMove) {
15727         if (gameInfo.resultDetails != NULL) {
15728             free(gameInfo.resultDetails);
15729             gameInfo.resultDetails = NULL;
15730             gameInfo.result = GameUnfinished;
15731         }
15732         forwardMostMove = currentMove;
15733         HistorySet(parseList, backwardMostMove, forwardMostMove,
15734                    currentMove-1);
15735     }
15736 }
15737
15738 void
15739 HintEvent ()
15740 {
15741     if (appData.noChessProgram) return;
15742     switch (gameMode) {
15743       case MachinePlaysWhite:
15744         if (WhiteOnMove(forwardMostMove)) {
15745             DisplayError(_("Wait until your turn."), 0);
15746             return;
15747         }
15748         break;
15749       case BeginningOfGame:
15750       case MachinePlaysBlack:
15751         if (!WhiteOnMove(forwardMostMove)) {
15752             DisplayError(_("Wait until your turn."), 0);
15753             return;
15754         }
15755         break;
15756       default:
15757         DisplayError(_("No hint available"), 0);
15758         return;
15759     }
15760     SendToProgram("hint\n", &first);
15761     hintRequested = TRUE;
15762 }
15763
15764 void
15765 CreateBookEvent ()
15766 {
15767     ListGame * lg = (ListGame *) gameList.head;
15768     FILE *f, *g;
15769     int nItem;
15770     static int secondTime = FALSE;
15771
15772     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15773         DisplayError(_("Game list not loaded or empty"), 0);
15774         return;
15775     }
15776
15777     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15778         fclose(g);
15779         secondTime++;
15780         DisplayNote(_("Book file exists! Try again for overwrite."));
15781         return;
15782     }
15783
15784     creatingBook = TRUE;
15785     secondTime = FALSE;
15786
15787     /* Get list size */
15788     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15789         LoadGame(f, nItem, "", TRUE);
15790         AddGameToBook(TRUE);
15791         lg = (ListGame *) lg->node.succ;
15792     }
15793
15794     creatingBook = FALSE;
15795     FlushBook();
15796 }
15797
15798 void
15799 BookEvent ()
15800 {
15801     if (appData.noChessProgram) return;
15802     switch (gameMode) {
15803       case MachinePlaysWhite:
15804         if (WhiteOnMove(forwardMostMove)) {
15805             DisplayError(_("Wait until your turn."), 0);
15806             return;
15807         }
15808         break;
15809       case BeginningOfGame:
15810       case MachinePlaysBlack:
15811         if (!WhiteOnMove(forwardMostMove)) {
15812             DisplayError(_("Wait until your turn."), 0);
15813             return;
15814         }
15815         break;
15816       case EditPosition:
15817         EditPositionDone(TRUE);
15818         break;
15819       case TwoMachinesPlay:
15820         return;
15821       default:
15822         break;
15823     }
15824     SendToProgram("bk\n", &first);
15825     bookOutput[0] = NULLCHAR;
15826     bookRequested = TRUE;
15827 }
15828
15829 void
15830 AboutGameEvent ()
15831 {
15832     char *tags = PGNTags(&gameInfo);
15833     TagsPopUp(tags, CmailMsg());
15834     free(tags);
15835 }
15836
15837 /* end button procedures */
15838
15839 void
15840 PrintPosition (FILE *fp, int move)
15841 {
15842     int i, j;
15843
15844     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15845         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15846             char c = PieceToChar(boards[move][i][j]);
15847             fputc(c == 'x' ? '.' : c, fp);
15848             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
15849         }
15850     }
15851     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
15852       fprintf(fp, "white to play\n");
15853     else
15854       fprintf(fp, "black to play\n");
15855 }
15856
15857 void
15858 PrintOpponents (FILE *fp)
15859 {
15860     if (gameInfo.white != NULL) {
15861         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
15862     } else {
15863         fprintf(fp, "\n");
15864     }
15865 }
15866
15867 /* Find last component of program's own name, using some heuristics */
15868 void
15869 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
15870 {
15871     char *p, *q, c;
15872     int local = (strcmp(host, "localhost") == 0);
15873     while (!local && (p = strchr(prog, ';')) != NULL) {
15874         p++;
15875         while (*p == ' ') p++;
15876         prog = p;
15877     }
15878     if (*prog == '"' || *prog == '\'') {
15879         q = strchr(prog + 1, *prog);
15880     } else {
15881         q = strchr(prog, ' ');
15882     }
15883     if (q == NULL) q = prog + strlen(prog);
15884     p = q;
15885     while (p >= prog && *p != '/' && *p != '\\') p--;
15886     p++;
15887     if(p == prog && *p == '"') p++;
15888     c = *q; *q = 0;
15889     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
15890     memcpy(buf, p, q - p);
15891     buf[q - p] = NULLCHAR;
15892     if (!local) {
15893         strcat(buf, "@");
15894         strcat(buf, host);
15895     }
15896 }
15897
15898 char *
15899 TimeControlTagValue ()
15900 {
15901     char buf[MSG_SIZ];
15902     if (!appData.clockMode) {
15903       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
15904     } else if (movesPerSession > 0) {
15905       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
15906     } else if (timeIncrement == 0) {
15907       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
15908     } else {
15909       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
15910     }
15911     return StrSave(buf);
15912 }
15913
15914 void
15915 SetGameInfo ()
15916 {
15917     /* This routine is used only for certain modes */
15918     VariantClass v = gameInfo.variant;
15919     ChessMove r = GameUnfinished;
15920     char *p = NULL;
15921
15922     if(keepInfo) return;
15923
15924     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
15925         r = gameInfo.result;
15926         p = gameInfo.resultDetails;
15927         gameInfo.resultDetails = NULL;
15928     }
15929     ClearGameInfo(&gameInfo);
15930     gameInfo.variant = v;
15931
15932     switch (gameMode) {
15933       case MachinePlaysWhite:
15934         gameInfo.event = StrSave( appData.pgnEventHeader );
15935         gameInfo.site = StrSave(HostName());
15936         gameInfo.date = PGNDate();
15937         gameInfo.round = StrSave("-");
15938         gameInfo.white = StrSave(first.tidy);
15939         gameInfo.black = StrSave(UserName());
15940         gameInfo.timeControl = TimeControlTagValue();
15941         break;
15942
15943       case MachinePlaysBlack:
15944         gameInfo.event = StrSave( appData.pgnEventHeader );
15945         gameInfo.site = StrSave(HostName());
15946         gameInfo.date = PGNDate();
15947         gameInfo.round = StrSave("-");
15948         gameInfo.white = StrSave(UserName());
15949         gameInfo.black = StrSave(first.tidy);
15950         gameInfo.timeControl = TimeControlTagValue();
15951         break;
15952
15953       case TwoMachinesPlay:
15954         gameInfo.event = StrSave( appData.pgnEventHeader );
15955         gameInfo.site = StrSave(HostName());
15956         gameInfo.date = PGNDate();
15957         if (roundNr > 0) {
15958             char buf[MSG_SIZ];
15959             snprintf(buf, MSG_SIZ, "%d", roundNr);
15960             gameInfo.round = StrSave(buf);
15961         } else {
15962             gameInfo.round = StrSave("-");
15963         }
15964         if (first.twoMachinesColor[0] == 'w') {
15965             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15966             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15967         } else {
15968             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
15969             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
15970         }
15971         gameInfo.timeControl = TimeControlTagValue();
15972         break;
15973
15974       case EditGame:
15975         gameInfo.event = StrSave("Edited game");
15976         gameInfo.site = StrSave(HostName());
15977         gameInfo.date = PGNDate();
15978         gameInfo.round = StrSave("-");
15979         gameInfo.white = StrSave("-");
15980         gameInfo.black = StrSave("-");
15981         gameInfo.result = r;
15982         gameInfo.resultDetails = p;
15983         break;
15984
15985       case EditPosition:
15986         gameInfo.event = StrSave("Edited position");
15987         gameInfo.site = StrSave(HostName());
15988         gameInfo.date = PGNDate();
15989         gameInfo.round = StrSave("-");
15990         gameInfo.white = StrSave("-");
15991         gameInfo.black = StrSave("-");
15992         break;
15993
15994       case IcsPlayingWhite:
15995       case IcsPlayingBlack:
15996       case IcsObserving:
15997       case IcsExamining:
15998         break;
15999
16000       case PlayFromGameFile:
16001         gameInfo.event = StrSave("Game from non-PGN file");
16002         gameInfo.site = StrSave(HostName());
16003         gameInfo.date = PGNDate();
16004         gameInfo.round = StrSave("-");
16005         gameInfo.white = StrSave("?");
16006         gameInfo.black = StrSave("?");
16007         break;
16008
16009       default:
16010         break;
16011     }
16012 }
16013
16014 void
16015 ReplaceComment (int index, char *text)
16016 {
16017     int len;
16018     char *p;
16019     float score;
16020
16021     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16022        pvInfoList[index-1].depth == len &&
16023        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16024        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16025     while (*text == '\n') text++;
16026     len = strlen(text);
16027     while (len > 0 && text[len - 1] == '\n') len--;
16028
16029     if (commentList[index] != NULL)
16030       free(commentList[index]);
16031
16032     if (len == 0) {
16033         commentList[index] = NULL;
16034         return;
16035     }
16036   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16037       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16038       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16039     commentList[index] = (char *) malloc(len + 2);
16040     strncpy(commentList[index], text, len);
16041     commentList[index][len] = '\n';
16042     commentList[index][len + 1] = NULLCHAR;
16043   } else {
16044     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16045     char *p;
16046     commentList[index] = (char *) malloc(len + 7);
16047     safeStrCpy(commentList[index], "{\n", 3);
16048     safeStrCpy(commentList[index]+2, text, len+1);
16049     commentList[index][len+2] = NULLCHAR;
16050     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16051     strcat(commentList[index], "\n}\n");
16052   }
16053 }
16054
16055 void
16056 CrushCRs (char *text)
16057 {
16058   char *p = text;
16059   char *q = text;
16060   char ch;
16061
16062   do {
16063     ch = *p++;
16064     if (ch == '\r') continue;
16065     *q++ = ch;
16066   } while (ch != '\0');
16067 }
16068
16069 void
16070 AppendComment (int index, char *text, Boolean addBraces)
16071 /* addBraces  tells if we should add {} */
16072 {
16073     int oldlen, len;
16074     char *old;
16075
16076 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16077     if(addBraces == 3) addBraces = 0; else // force appending literally
16078     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16079
16080     CrushCRs(text);
16081     while (*text == '\n') text++;
16082     len = strlen(text);
16083     while (len > 0 && text[len - 1] == '\n') len--;
16084     text[len] = NULLCHAR;
16085
16086     if (len == 0) return;
16087
16088     if (commentList[index] != NULL) {
16089       Boolean addClosingBrace = addBraces;
16090         old = commentList[index];
16091         oldlen = strlen(old);
16092         while(commentList[index][oldlen-1] ==  '\n')
16093           commentList[index][--oldlen] = NULLCHAR;
16094         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16095         safeStrCpy(commentList[index], old, oldlen + len + 6);
16096         free(old);
16097         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16098         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16099           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16100           while (*text == '\n') { text++; len--; }
16101           commentList[index][--oldlen] = NULLCHAR;
16102       }
16103         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16104         else          strcat(commentList[index], "\n");
16105         strcat(commentList[index], text);
16106         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16107         else          strcat(commentList[index], "\n");
16108     } else {
16109         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16110         if(addBraces)
16111           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16112         else commentList[index][0] = NULLCHAR;
16113         strcat(commentList[index], text);
16114         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16115         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16116     }
16117 }
16118
16119 static char *
16120 FindStr (char * text, char * sub_text)
16121 {
16122     char * result = strstr( text, sub_text );
16123
16124     if( result != NULL ) {
16125         result += strlen( sub_text );
16126     }
16127
16128     return result;
16129 }
16130
16131 /* [AS] Try to extract PV info from PGN comment */
16132 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16133 char *
16134 GetInfoFromComment (int index, char * text)
16135 {
16136     char * sep = text, *p;
16137
16138     if( text != NULL && index > 0 ) {
16139         int score = 0;
16140         int depth = 0;
16141         int time = -1, sec = 0, deci;
16142         char * s_eval = FindStr( text, "[%eval " );
16143         char * s_emt = FindStr( text, "[%emt " );
16144 #if 0
16145         if( s_eval != NULL || s_emt != NULL ) {
16146 #else
16147         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16148 #endif
16149             /* New style */
16150             char delim;
16151
16152             if( s_eval != NULL ) {
16153                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16154                     return text;
16155                 }
16156
16157                 if( delim != ']' ) {
16158                     return text;
16159                 }
16160             }
16161
16162             if( s_emt != NULL ) {
16163             }
16164                 return text;
16165         }
16166         else {
16167             /* We expect something like: [+|-]nnn.nn/dd */
16168             int score_lo = 0;
16169
16170             if(*text != '{') return text; // [HGM] braces: must be normal comment
16171
16172             sep = strchr( text, '/' );
16173             if( sep == NULL || sep < (text+4) ) {
16174                 return text;
16175             }
16176
16177             p = text;
16178             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16179             if(p[1] == '(') { // comment starts with PV
16180                p = strchr(p, ')'); // locate end of PV
16181                if(p == NULL || sep < p+5) return text;
16182                // at this point we have something like "{(.*) +0.23/6 ..."
16183                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16184                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16185                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16186             }
16187             time = -1; sec = -1; deci = -1;
16188             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16189                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16190                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16191                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16192                 return text;
16193             }
16194
16195             if( score_lo < 0 || score_lo >= 100 ) {
16196                 return text;
16197             }
16198
16199             if(sec >= 0) time = 600*time + 10*sec; else
16200             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16201
16202             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16203
16204             /* [HGM] PV time: now locate end of PV info */
16205             while( *++sep >= '0' && *sep <= '9'); // strip depth
16206             if(time >= 0)
16207             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16208             if(sec >= 0)
16209             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16210             if(deci >= 0)
16211             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16212             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16213         }
16214
16215         if( depth <= 0 ) {
16216             return text;
16217         }
16218
16219         if( time < 0 ) {
16220             time = -1;
16221         }
16222
16223         pvInfoList[index-1].depth = depth;
16224         pvInfoList[index-1].score = score;
16225         pvInfoList[index-1].time  = 10*time; // centi-sec
16226         if(*sep == '}') *sep = 0; else *--sep = '{';
16227         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16228     }
16229     return sep;
16230 }
16231
16232 void
16233 SendToProgram (char *message, ChessProgramState *cps)
16234 {
16235     int count, outCount, error;
16236     char buf[MSG_SIZ];
16237
16238     if (cps->pr == NoProc) return;
16239     Attention(cps);
16240
16241     if (appData.debugMode) {
16242         TimeMark now;
16243         GetTimeMark(&now);
16244         fprintf(debugFP, "%ld >%-6s: %s",
16245                 SubtractTimeMarks(&now, &programStartTime),
16246                 cps->which, message);
16247         if(serverFP)
16248             fprintf(serverFP, "%ld >%-6s: %s",
16249                 SubtractTimeMarks(&now, &programStartTime),
16250                 cps->which, message), fflush(serverFP);
16251     }
16252
16253     count = strlen(message);
16254     outCount = OutputToProcess(cps->pr, message, count, &error);
16255     if (outCount < count && !exiting
16256                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16257       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16258       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16259         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16260             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16261                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16262                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16263                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16264             } else {
16265                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16266                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16267                 gameInfo.result = res;
16268             }
16269             gameInfo.resultDetails = StrSave(buf);
16270         }
16271         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16272         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16273     }
16274 }
16275
16276 void
16277 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16278 {
16279     char *end_str;
16280     char buf[MSG_SIZ];
16281     ChessProgramState *cps = (ChessProgramState *)closure;
16282
16283     if (isr != cps->isr) return; /* Killed intentionally */
16284     if (count <= 0) {
16285         if (count == 0) {
16286             RemoveInputSource(cps->isr);
16287             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16288                     _(cps->which), cps->program);
16289             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16290             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16291                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16292                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16293                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16294                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16295                 } else {
16296                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16297                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16298                     gameInfo.result = res;
16299                 }
16300                 gameInfo.resultDetails = StrSave(buf);
16301             }
16302             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16303             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16304         } else {
16305             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16306                     _(cps->which), cps->program);
16307             RemoveInputSource(cps->isr);
16308
16309             /* [AS] Program is misbehaving badly... kill it */
16310             if( count == -2 ) {
16311                 DestroyChildProcess( cps->pr, 9 );
16312                 cps->pr = NoProc;
16313             }
16314
16315             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16316         }
16317         return;
16318     }
16319
16320     if ((end_str = strchr(message, '\r')) != NULL)
16321       *end_str = NULLCHAR;
16322     if ((end_str = strchr(message, '\n')) != NULL)
16323       *end_str = NULLCHAR;
16324
16325     if (appData.debugMode) {
16326         TimeMark now; int print = 1;
16327         char *quote = ""; char c; int i;
16328
16329         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16330                 char start = message[0];
16331                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16332                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16333                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16334                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16335                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16336                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16337                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16338                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16339                    sscanf(message, "hint: %c", &c)!=1 &&
16340                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16341                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16342                     print = (appData.engineComments >= 2);
16343                 }
16344                 message[0] = start; // restore original message
16345         }
16346         if(print) {
16347                 GetTimeMark(&now);
16348                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16349                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16350                         quote,
16351                         message);
16352                 if(serverFP)
16353                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16354                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16355                         quote,
16356                         message), fflush(serverFP);
16357         }
16358     }
16359
16360     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16361     if (appData.icsEngineAnalyze) {
16362         if (strstr(message, "whisper") != NULL ||
16363              strstr(message, "kibitz") != NULL ||
16364             strstr(message, "tellics") != NULL) return;
16365     }
16366
16367     HandleMachineMove(message, cps);
16368 }
16369
16370
16371 void
16372 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16373 {
16374     char buf[MSG_SIZ];
16375     int seconds;
16376
16377     if( timeControl_2 > 0 ) {
16378         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16379             tc = timeControl_2;
16380         }
16381     }
16382     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16383     inc /= cps->timeOdds;
16384     st  /= cps->timeOdds;
16385
16386     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16387
16388     if (st > 0) {
16389       /* Set exact time per move, normally using st command */
16390       if (cps->stKludge) {
16391         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16392         seconds = st % 60;
16393         if (seconds == 0) {
16394           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16395         } else {
16396           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16397         }
16398       } else {
16399         snprintf(buf, MSG_SIZ, "st %d\n", st);
16400       }
16401     } else {
16402       /* Set conventional or incremental time control, using level command */
16403       if (seconds == 0) {
16404         /* Note old gnuchess bug -- minutes:seconds used to not work.
16405            Fixed in later versions, but still avoid :seconds
16406            when seconds is 0. */
16407         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16408       } else {
16409         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16410                  seconds, inc/1000.);
16411       }
16412     }
16413     SendToProgram(buf, cps);
16414
16415     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16416     /* Orthogonally, limit search to given depth */
16417     if (sd > 0) {
16418       if (cps->sdKludge) {
16419         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16420       } else {
16421         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16422       }
16423       SendToProgram(buf, cps);
16424     }
16425
16426     if(cps->nps >= 0) { /* [HGM] nps */
16427         if(cps->supportsNPS == FALSE)
16428           cps->nps = -1; // don't use if engine explicitly says not supported!
16429         else {
16430           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16431           SendToProgram(buf, cps);
16432         }
16433     }
16434 }
16435
16436 ChessProgramState *
16437 WhitePlayer ()
16438 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16439 {
16440     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16441        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16442         return &second;
16443     return &first;
16444 }
16445
16446 void
16447 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16448 {
16449     char message[MSG_SIZ];
16450     long time, otime;
16451
16452     /* Note: this routine must be called when the clocks are stopped
16453        or when they have *just* been set or switched; otherwise
16454        it will be off by the time since the current tick started.
16455     */
16456     if (machineWhite) {
16457         time = whiteTimeRemaining / 10;
16458         otime = blackTimeRemaining / 10;
16459     } else {
16460         time = blackTimeRemaining / 10;
16461         otime = whiteTimeRemaining / 10;
16462     }
16463     /* [HGM] translate opponent's time by time-odds factor */
16464     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16465
16466     if (time <= 0) time = 1;
16467     if (otime <= 0) otime = 1;
16468
16469     snprintf(message, MSG_SIZ, "time %ld\n", time);
16470     SendToProgram(message, cps);
16471
16472     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16473     SendToProgram(message, cps);
16474 }
16475
16476 char *
16477 EngineDefinedVariant (ChessProgramState *cps, int n)
16478 {   // return name of n-th unknown variant that engine supports
16479     static char buf[MSG_SIZ];
16480     char *p, *s = cps->variants;
16481     if(!s) return NULL;
16482     do { // parse string from variants feature
16483       VariantClass v;
16484         p = strchr(s, ',');
16485         if(p) *p = NULLCHAR;
16486       v = StringToVariant(s);
16487       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16488         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16489             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16490         }
16491         if(p) *p++ = ',';
16492         if(n < 0) return buf;
16493     } while(s = p);
16494     return NULL;
16495 }
16496
16497 int
16498 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16499 {
16500   char buf[MSG_SIZ];
16501   int len = strlen(name);
16502   int val;
16503
16504   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16505     (*p) += len + 1;
16506     sscanf(*p, "%d", &val);
16507     *loc = (val != 0);
16508     while (**p && **p != ' ')
16509       (*p)++;
16510     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16511     SendToProgram(buf, cps);
16512     return TRUE;
16513   }
16514   return FALSE;
16515 }
16516
16517 int
16518 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16519 {
16520   char buf[MSG_SIZ];
16521   int len = strlen(name);
16522   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16523     (*p) += len + 1;
16524     sscanf(*p, "%d", loc);
16525     while (**p && **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 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16535 {
16536   char buf[MSG_SIZ];
16537   int len = strlen(name);
16538   if (strncmp((*p), name, len) == 0
16539       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16540     (*p) += len + 2;
16541     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16542     sscanf(*p, "%[^\"]", *loc);
16543     while (**p && **p != '\"') (*p)++;
16544     if (**p == '\"') (*p)++;
16545     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16546     SendToProgram(buf, cps);
16547     return TRUE;
16548   }
16549   return FALSE;
16550 }
16551
16552 int
16553 ParseOption (Option *opt, ChessProgramState *cps)
16554 // [HGM] options: process the string that defines an engine option, and determine
16555 // name, type, default value, and allowed value range
16556 {
16557         char *p, *q, buf[MSG_SIZ];
16558         int n, min = (-1)<<31, max = 1<<31, def;
16559
16560         if(p = strstr(opt->name, " -spin ")) {
16561             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16562             if(max < min) max = min; // enforce consistency
16563             if(def < min) def = min;
16564             if(def > max) def = max;
16565             opt->value = def;
16566             opt->min = min;
16567             opt->max = max;
16568             opt->type = Spin;
16569         } else if((p = strstr(opt->name, " -slider "))) {
16570             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16571             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16572             if(max < min) max = min; // enforce consistency
16573             if(def < min) def = min;
16574             if(def > max) def = max;
16575             opt->value = def;
16576             opt->min = min;
16577             opt->max = max;
16578             opt->type = Spin; // Slider;
16579         } else if((p = strstr(opt->name, " -string "))) {
16580             opt->textValue = p+9;
16581             opt->type = TextBox;
16582         } else if((p = strstr(opt->name, " -file "))) {
16583             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16584             opt->textValue = p+7;
16585             opt->type = FileName; // FileName;
16586         } else if((p = strstr(opt->name, " -path "))) {
16587             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16588             opt->textValue = p+7;
16589             opt->type = PathName; // PathName;
16590         } else if(p = strstr(opt->name, " -check ")) {
16591             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16592             opt->value = (def != 0);
16593             opt->type = CheckBox;
16594         } else if(p = strstr(opt->name, " -combo ")) {
16595             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16596             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16597             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16598             opt->value = n = 0;
16599             while(q = StrStr(q, " /// ")) {
16600                 n++; *q = 0;    // count choices, and null-terminate each of them
16601                 q += 5;
16602                 if(*q == '*') { // remember default, which is marked with * prefix
16603                     q++;
16604                     opt->value = n;
16605                 }
16606                 cps->comboList[cps->comboCnt++] = q;
16607             }
16608             cps->comboList[cps->comboCnt++] = NULL;
16609             opt->max = n + 1;
16610             opt->type = ComboBox;
16611         } else if(p = strstr(opt->name, " -button")) {
16612             opt->type = Button;
16613         } else if(p = strstr(opt->name, " -save")) {
16614             opt->type = SaveButton;
16615         } else return FALSE;
16616         *p = 0; // terminate option name
16617         // now look if the command-line options define a setting for this engine option.
16618         if(cps->optionSettings && cps->optionSettings[0])
16619             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16620         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16621           snprintf(buf, MSG_SIZ, "option %s", p);
16622                 if(p = strstr(buf, ",")) *p = 0;
16623                 if(q = strchr(buf, '=')) switch(opt->type) {
16624                     case ComboBox:
16625                         for(n=0; n<opt->max; n++)
16626                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16627                         break;
16628                     case TextBox:
16629                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16630                         break;
16631                     case Spin:
16632                     case CheckBox:
16633                         opt->value = atoi(q+1);
16634                     default:
16635                         break;
16636                 }
16637                 strcat(buf, "\n");
16638                 SendToProgram(buf, cps);
16639         }
16640         return TRUE;
16641 }
16642
16643 void
16644 FeatureDone (ChessProgramState *cps, int val)
16645 {
16646   DelayedEventCallback cb = GetDelayedEvent();
16647   if ((cb == InitBackEnd3 && cps == &first) ||
16648       (cb == SettingsMenuIfReady && cps == &second) ||
16649       (cb == LoadEngine) ||
16650       (cb == TwoMachinesEventIfReady)) {
16651     CancelDelayedEvent();
16652     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16653   }
16654   cps->initDone = val;
16655   if(val) cps->reload = FALSE;
16656 }
16657
16658 /* Parse feature command from engine */
16659 void
16660 ParseFeatures (char *args, ChessProgramState *cps)
16661 {
16662   char *p = args;
16663   char *q = NULL;
16664   int val;
16665   char buf[MSG_SIZ];
16666
16667   for (;;) {
16668     while (*p == ' ') p++;
16669     if (*p == NULLCHAR) return;
16670
16671     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16672     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16673     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16674     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16675     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16676     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16677     if (BoolFeature(&p, "reuse", &val, cps)) {
16678       /* Engine can disable reuse, but can't enable it if user said no */
16679       if (!val) cps->reuse = FALSE;
16680       continue;
16681     }
16682     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16683     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16684       if (gameMode == TwoMachinesPlay) {
16685         DisplayTwoMachinesTitle();
16686       } else {
16687         DisplayTitle("");
16688       }
16689       continue;
16690     }
16691     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16692     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16693     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16694     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16695     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16696     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16697     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16698     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16699     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16700     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16701     if (IntFeature(&p, "done", &val, cps)) {
16702       FeatureDone(cps, val);
16703       continue;
16704     }
16705     /* Added by Tord: */
16706     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16707     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16708     /* End of additions by Tord */
16709
16710     /* [HGM] added features: */
16711     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16712     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16713     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16714     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16715     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16716     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16717     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16718     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16719         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16720         FREE(cps->option[cps->nrOptions].name);
16721         cps->option[cps->nrOptions].name = q; q = NULL;
16722         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16723           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16724             SendToProgram(buf, cps);
16725             continue;
16726         }
16727         if(cps->nrOptions >= MAX_OPTIONS) {
16728             cps->nrOptions--;
16729             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16730             DisplayError(buf, 0);
16731         }
16732         continue;
16733     }
16734     /* End of additions by HGM */
16735
16736     /* unknown feature: complain and skip */
16737     q = p;
16738     while (*q && *q != '=') q++;
16739     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16740     SendToProgram(buf, cps);
16741     p = q;
16742     if (*p == '=') {
16743       p++;
16744       if (*p == '\"') {
16745         p++;
16746         while (*p && *p != '\"') p++;
16747         if (*p == '\"') p++;
16748       } else {
16749         while (*p && *p != ' ') p++;
16750       }
16751     }
16752   }
16753
16754 }
16755
16756 void
16757 PeriodicUpdatesEvent (int newState)
16758 {
16759     if (newState == appData.periodicUpdates)
16760       return;
16761
16762     appData.periodicUpdates=newState;
16763
16764     /* Display type changes, so update it now */
16765 //    DisplayAnalysis();
16766
16767     /* Get the ball rolling again... */
16768     if (newState) {
16769         AnalysisPeriodicEvent(1);
16770         StartAnalysisClock();
16771     }
16772 }
16773
16774 void
16775 PonderNextMoveEvent (int newState)
16776 {
16777     if (newState == appData.ponderNextMove) return;
16778     if (gameMode == EditPosition) EditPositionDone(TRUE);
16779     if (newState) {
16780         SendToProgram("hard\n", &first);
16781         if (gameMode == TwoMachinesPlay) {
16782             SendToProgram("hard\n", &second);
16783         }
16784     } else {
16785         SendToProgram("easy\n", &first);
16786         thinkOutput[0] = NULLCHAR;
16787         if (gameMode == TwoMachinesPlay) {
16788             SendToProgram("easy\n", &second);
16789         }
16790     }
16791     appData.ponderNextMove = newState;
16792 }
16793
16794 void
16795 NewSettingEvent (int option, int *feature, char *command, int value)
16796 {
16797     char buf[MSG_SIZ];
16798
16799     if (gameMode == EditPosition) EditPositionDone(TRUE);
16800     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
16801     if(feature == NULL || *feature) SendToProgram(buf, &first);
16802     if (gameMode == TwoMachinesPlay) {
16803         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
16804     }
16805 }
16806
16807 void
16808 ShowThinkingEvent ()
16809 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
16810 {
16811     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
16812     int newState = appData.showThinking
16813         // [HGM] thinking: other features now need thinking output as well
16814         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
16815
16816     if (oldState == newState) return;
16817     oldState = newState;
16818     if (gameMode == EditPosition) EditPositionDone(TRUE);
16819     if (oldState) {
16820         SendToProgram("post\n", &first);
16821         if (gameMode == TwoMachinesPlay) {
16822             SendToProgram("post\n", &second);
16823         }
16824     } else {
16825         SendToProgram("nopost\n", &first);
16826         thinkOutput[0] = NULLCHAR;
16827         if (gameMode == TwoMachinesPlay) {
16828             SendToProgram("nopost\n", &second);
16829         }
16830     }
16831 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
16832 }
16833
16834 void
16835 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
16836 {
16837   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
16838   if (pr == NoProc) return;
16839   AskQuestion(title, question, replyPrefix, pr);
16840 }
16841
16842 void
16843 TypeInEvent (char firstChar)
16844 {
16845     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
16846         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
16847         gameMode == AnalyzeMode || gameMode == EditGame ||
16848         gameMode == EditPosition || gameMode == IcsExamining ||
16849         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
16850         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
16851                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
16852                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
16853         gameMode == Training) PopUpMoveDialog(firstChar);
16854 }
16855
16856 void
16857 TypeInDoneEvent (char *move)
16858 {
16859         Board board;
16860         int n, fromX, fromY, toX, toY;
16861         char promoChar;
16862         ChessMove moveType;
16863
16864         // [HGM] FENedit
16865         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
16866                 EditPositionPasteFEN(move);
16867                 return;
16868         }
16869         // [HGM] movenum: allow move number to be typed in any mode
16870         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
16871           ToNrEvent(2*n-1);
16872           return;
16873         }
16874         // undocumented kludge: allow command-line option to be typed in!
16875         // (potentially fatal, and does not implement the effect of the option.)
16876         // should only be used for options that are values on which future decisions will be made,
16877         // and definitely not on options that would be used during initialization.
16878         if(strstr(move, "!!! -") == move) {
16879             ParseArgsFromString(move+4);
16880             return;
16881         }
16882
16883       if (gameMode != EditGame && currentMove != forwardMostMove &&
16884         gameMode != Training) {
16885         DisplayMoveError(_("Displayed move is not current"));
16886       } else {
16887         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16888           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
16889         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
16890         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
16891           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
16892           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
16893         } else {
16894           DisplayMoveError(_("Could not parse move"));
16895         }
16896       }
16897 }
16898
16899 void
16900 DisplayMove (int moveNumber)
16901 {
16902     char message[MSG_SIZ];
16903     char res[MSG_SIZ];
16904     char cpThinkOutput[MSG_SIZ];
16905
16906     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
16907
16908     if (moveNumber == forwardMostMove - 1 ||
16909         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
16910
16911         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
16912
16913         if (strchr(cpThinkOutput, '\n')) {
16914             *strchr(cpThinkOutput, '\n') = NULLCHAR;
16915         }
16916     } else {
16917         *cpThinkOutput = NULLCHAR;
16918     }
16919
16920     /* [AS] Hide thinking from human user */
16921     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
16922         *cpThinkOutput = NULLCHAR;
16923         if( thinkOutput[0] != NULLCHAR ) {
16924             int i;
16925
16926             for( i=0; i<=hiddenThinkOutputState; i++ ) {
16927                 cpThinkOutput[i] = '.';
16928             }
16929             cpThinkOutput[i] = NULLCHAR;
16930             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
16931         }
16932     }
16933
16934     if (moveNumber == forwardMostMove - 1 &&
16935         gameInfo.resultDetails != NULL) {
16936         if (gameInfo.resultDetails[0] == NULLCHAR) {
16937           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
16938         } else {
16939           snprintf(res, MSG_SIZ, " {%s} %s",
16940                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
16941         }
16942     } else {
16943         res[0] = NULLCHAR;
16944     }
16945
16946     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16947         DisplayMessage(res, cpThinkOutput);
16948     } else {
16949       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
16950                 WhiteOnMove(moveNumber) ? " " : ".. ",
16951                 parseList[moveNumber], res);
16952         DisplayMessage(message, cpThinkOutput);
16953     }
16954 }
16955
16956 void
16957 DisplayComment (int moveNumber, char *text)
16958 {
16959     char title[MSG_SIZ];
16960
16961     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
16962       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
16963     } else {
16964       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
16965               WhiteOnMove(moveNumber) ? " " : ".. ",
16966               parseList[moveNumber]);
16967     }
16968     if (text != NULL && (appData.autoDisplayComment || commentUp))
16969         CommentPopUp(title, text);
16970 }
16971
16972 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
16973  * might be busy thinking or pondering.  It can be omitted if your
16974  * gnuchess is configured to stop thinking immediately on any user
16975  * input.  However, that gnuchess feature depends on the FIONREAD
16976  * ioctl, which does not work properly on some flavors of Unix.
16977  */
16978 void
16979 Attention (ChessProgramState *cps)
16980 {
16981 #if ATTENTION
16982     if (!cps->useSigint) return;
16983     if (appData.noChessProgram || (cps->pr == NoProc)) return;
16984     switch (gameMode) {
16985       case MachinePlaysWhite:
16986       case MachinePlaysBlack:
16987       case TwoMachinesPlay:
16988       case IcsPlayingWhite:
16989       case IcsPlayingBlack:
16990       case AnalyzeMode:
16991       case AnalyzeFile:
16992         /* Skip if we know it isn't thinking */
16993         if (!cps->maybeThinking) return;
16994         if (appData.debugMode)
16995           fprintf(debugFP, "Interrupting %s\n", cps->which);
16996         InterruptChildProcess(cps->pr);
16997         cps->maybeThinking = FALSE;
16998         break;
16999       default:
17000         break;
17001     }
17002 #endif /*ATTENTION*/
17003 }
17004
17005 int
17006 CheckFlags ()
17007 {
17008     if (whiteTimeRemaining <= 0) {
17009         if (!whiteFlag) {
17010             whiteFlag = TRUE;
17011             if (appData.icsActive) {
17012                 if (appData.autoCallFlag &&
17013                     gameMode == IcsPlayingBlack && !blackFlag) {
17014                   SendToICS(ics_prefix);
17015                   SendToICS("flag\n");
17016                 }
17017             } else {
17018                 if (blackFlag) {
17019                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17020                 } else {
17021                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17022                     if (appData.autoCallFlag) {
17023                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17024                         return TRUE;
17025                     }
17026                 }
17027             }
17028         }
17029     }
17030     if (blackTimeRemaining <= 0) {
17031         if (!blackFlag) {
17032             blackFlag = TRUE;
17033             if (appData.icsActive) {
17034                 if (appData.autoCallFlag &&
17035                     gameMode == IcsPlayingWhite && !whiteFlag) {
17036                   SendToICS(ics_prefix);
17037                   SendToICS("flag\n");
17038                 }
17039             } else {
17040                 if (whiteFlag) {
17041                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17042                 } else {
17043                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17044                     if (appData.autoCallFlag) {
17045                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17046                         return TRUE;
17047                     }
17048                 }
17049             }
17050         }
17051     }
17052     return FALSE;
17053 }
17054
17055 void
17056 CheckTimeControl ()
17057 {
17058     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17059         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17060
17061     /*
17062      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17063      */
17064     if ( !WhiteOnMove(forwardMostMove) ) {
17065         /* White made time control */
17066         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17067         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17068         /* [HGM] time odds: correct new time quota for time odds! */
17069                                             / WhitePlayer()->timeOdds;
17070         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17071     } else {
17072         lastBlack -= blackTimeRemaining;
17073         /* Black made time control */
17074         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17075                                             / WhitePlayer()->other->timeOdds;
17076         lastWhite = whiteTimeRemaining;
17077     }
17078 }
17079
17080 void
17081 DisplayBothClocks ()
17082 {
17083     int wom = gameMode == EditPosition ?
17084       !blackPlaysFirst : WhiteOnMove(currentMove);
17085     DisplayWhiteClock(whiteTimeRemaining, wom);
17086     DisplayBlackClock(blackTimeRemaining, !wom);
17087 }
17088
17089
17090 /* Timekeeping seems to be a portability nightmare.  I think everyone
17091    has ftime(), but I'm really not sure, so I'm including some ifdefs
17092    to use other calls if you don't.  Clocks will be less accurate if
17093    you have neither ftime nor gettimeofday.
17094 */
17095
17096 /* VS 2008 requires the #include outside of the function */
17097 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17098 #include <sys/timeb.h>
17099 #endif
17100
17101 /* Get the current time as a TimeMark */
17102 void
17103 GetTimeMark (TimeMark *tm)
17104 {
17105 #if HAVE_GETTIMEOFDAY
17106
17107     struct timeval timeVal;
17108     struct timezone timeZone;
17109
17110     gettimeofday(&timeVal, &timeZone);
17111     tm->sec = (long) timeVal.tv_sec;
17112     tm->ms = (int) (timeVal.tv_usec / 1000L);
17113
17114 #else /*!HAVE_GETTIMEOFDAY*/
17115 #if HAVE_FTIME
17116
17117 // include <sys/timeb.h> / moved to just above start of function
17118     struct timeb timeB;
17119
17120     ftime(&timeB);
17121     tm->sec = (long) timeB.time;
17122     tm->ms = (int) timeB.millitm;
17123
17124 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17125     tm->sec = (long) time(NULL);
17126     tm->ms = 0;
17127 #endif
17128 #endif
17129 }
17130
17131 /* Return the difference in milliseconds between two
17132    time marks.  We assume the difference will fit in a long!
17133 */
17134 long
17135 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17136 {
17137     return 1000L*(tm2->sec - tm1->sec) +
17138            (long) (tm2->ms - tm1->ms);
17139 }
17140
17141
17142 /*
17143  * Code to manage the game clocks.
17144  *
17145  * In tournament play, black starts the clock and then white makes a move.
17146  * We give the human user a slight advantage if he is playing white---the
17147  * clocks don't run until he makes his first move, so it takes zero time.
17148  * Also, we don't account for network lag, so we could get out of sync
17149  * with GNU Chess's clock -- but then, referees are always right.
17150  */
17151
17152 static TimeMark tickStartTM;
17153 static long intendedTickLength;
17154
17155 long
17156 NextTickLength (long timeRemaining)
17157 {
17158     long nominalTickLength, nextTickLength;
17159
17160     if (timeRemaining > 0L && timeRemaining <= 10000L)
17161       nominalTickLength = 100L;
17162     else
17163       nominalTickLength = 1000L;
17164     nextTickLength = timeRemaining % nominalTickLength;
17165     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17166
17167     return nextTickLength;
17168 }
17169
17170 /* Adjust clock one minute up or down */
17171 void
17172 AdjustClock (Boolean which, int dir)
17173 {
17174     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17175     if(which) blackTimeRemaining += 60000*dir;
17176     else      whiteTimeRemaining += 60000*dir;
17177     DisplayBothClocks();
17178     adjustedClock = TRUE;
17179 }
17180
17181 /* Stop clocks and reset to a fresh time control */
17182 void
17183 ResetClocks ()
17184 {
17185     (void) StopClockTimer();
17186     if (appData.icsActive) {
17187         whiteTimeRemaining = blackTimeRemaining = 0;
17188     } else if (searchTime) {
17189         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17190         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17191     } else { /* [HGM] correct new time quote for time odds */
17192         whiteTC = blackTC = fullTimeControlString;
17193         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17194         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17195     }
17196     if (whiteFlag || blackFlag) {
17197         DisplayTitle("");
17198         whiteFlag = blackFlag = FALSE;
17199     }
17200     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17201     DisplayBothClocks();
17202     adjustedClock = FALSE;
17203 }
17204
17205 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17206
17207 /* Decrement running clock by amount of time that has passed */
17208 void
17209 DecrementClocks ()
17210 {
17211     long timeRemaining;
17212     long lastTickLength, fudge;
17213     TimeMark now;
17214
17215     if (!appData.clockMode) return;
17216     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17217
17218     GetTimeMark(&now);
17219
17220     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17221
17222     /* Fudge if we woke up a little too soon */
17223     fudge = intendedTickLength - lastTickLength;
17224     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17225
17226     if (WhiteOnMove(forwardMostMove)) {
17227         if(whiteNPS >= 0) lastTickLength = 0;
17228         timeRemaining = whiteTimeRemaining -= lastTickLength;
17229         if(timeRemaining < 0 && !appData.icsActive) {
17230             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17231             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17232                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17233                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17234             }
17235         }
17236         DisplayWhiteClock(whiteTimeRemaining - fudge,
17237                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17238     } else {
17239         if(blackNPS >= 0) lastTickLength = 0;
17240         timeRemaining = blackTimeRemaining -= lastTickLength;
17241         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17242             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17243             if(suddenDeath) {
17244                 blackStartMove = forwardMostMove;
17245                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17246             }
17247         }
17248         DisplayBlackClock(blackTimeRemaining - fudge,
17249                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17250     }
17251     if (CheckFlags()) return;
17252
17253     if(twoBoards) { // count down secondary board's clocks as well
17254         activePartnerTime -= lastTickLength;
17255         partnerUp = 1;
17256         if(activePartner == 'W')
17257             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17258         else
17259             DisplayBlackClock(activePartnerTime, TRUE);
17260         partnerUp = 0;
17261     }
17262
17263     tickStartTM = now;
17264     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17265     StartClockTimer(intendedTickLength);
17266
17267     /* if the time remaining has fallen below the alarm threshold, sound the
17268      * alarm. if the alarm has sounded and (due to a takeback or time control
17269      * with increment) the time remaining has increased to a level above the
17270      * threshold, reset the alarm so it can sound again.
17271      */
17272
17273     if (appData.icsActive && appData.icsAlarm) {
17274
17275         /* make sure we are dealing with the user's clock */
17276         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17277                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17278            )) return;
17279
17280         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17281             alarmSounded = FALSE;
17282         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17283             PlayAlarmSound();
17284             alarmSounded = TRUE;
17285         }
17286     }
17287 }
17288
17289
17290 /* A player has just moved, so stop the previously running
17291    clock and (if in clock mode) start the other one.
17292    We redisplay both clocks in case we're in ICS mode, because
17293    ICS gives us an update to both clocks after every move.
17294    Note that this routine is called *after* forwardMostMove
17295    is updated, so the last fractional tick must be subtracted
17296    from the color that is *not* on move now.
17297 */
17298 void
17299 SwitchClocks (int newMoveNr)
17300 {
17301     long lastTickLength;
17302     TimeMark now;
17303     int flagged = FALSE;
17304
17305     GetTimeMark(&now);
17306
17307     if (StopClockTimer() && appData.clockMode) {
17308         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17309         if (!WhiteOnMove(forwardMostMove)) {
17310             if(blackNPS >= 0) lastTickLength = 0;
17311             blackTimeRemaining -= lastTickLength;
17312            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17313 //         if(pvInfoList[forwardMostMove].time == -1)
17314                  pvInfoList[forwardMostMove].time =               // use GUI time
17315                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17316         } else {
17317            if(whiteNPS >= 0) lastTickLength = 0;
17318            whiteTimeRemaining -= lastTickLength;
17319            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17320 //         if(pvInfoList[forwardMostMove].time == -1)
17321                  pvInfoList[forwardMostMove].time =
17322                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17323         }
17324         flagged = CheckFlags();
17325     }
17326     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17327     CheckTimeControl();
17328
17329     if (flagged || !appData.clockMode) return;
17330
17331     switch (gameMode) {
17332       case MachinePlaysBlack:
17333       case MachinePlaysWhite:
17334       case BeginningOfGame:
17335         if (pausing) return;
17336         break;
17337
17338       case EditGame:
17339       case PlayFromGameFile:
17340       case IcsExamining:
17341         return;
17342
17343       default:
17344         break;
17345     }
17346
17347     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17348         if(WhiteOnMove(forwardMostMove))
17349              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17350         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17351     }
17352
17353     tickStartTM = now;
17354     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17355       whiteTimeRemaining : blackTimeRemaining);
17356     StartClockTimer(intendedTickLength);
17357 }
17358
17359
17360 /* Stop both clocks */
17361 void
17362 StopClocks ()
17363 {
17364     long lastTickLength;
17365     TimeMark now;
17366
17367     if (!StopClockTimer()) return;
17368     if (!appData.clockMode) return;
17369
17370     GetTimeMark(&now);
17371
17372     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17373     if (WhiteOnMove(forwardMostMove)) {
17374         if(whiteNPS >= 0) lastTickLength = 0;
17375         whiteTimeRemaining -= lastTickLength;
17376         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17377     } else {
17378         if(blackNPS >= 0) lastTickLength = 0;
17379         blackTimeRemaining -= lastTickLength;
17380         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17381     }
17382     CheckFlags();
17383 }
17384
17385 /* Start clock of player on move.  Time may have been reset, so
17386    if clock is already running, stop and restart it. */
17387 void
17388 StartClocks ()
17389 {
17390     (void) StopClockTimer(); /* in case it was running already */
17391     DisplayBothClocks();
17392     if (CheckFlags()) return;
17393
17394     if (!appData.clockMode) return;
17395     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17396
17397     GetTimeMark(&tickStartTM);
17398     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17399       whiteTimeRemaining : blackTimeRemaining);
17400
17401    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17402     whiteNPS = blackNPS = -1;
17403     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17404        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17405         whiteNPS = first.nps;
17406     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17407        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17408         blackNPS = first.nps;
17409     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17410         whiteNPS = second.nps;
17411     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17412         blackNPS = second.nps;
17413     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17414
17415     StartClockTimer(intendedTickLength);
17416 }
17417
17418 char *
17419 TimeString (long ms)
17420 {
17421     long second, minute, hour, day;
17422     char *sign = "";
17423     static char buf[32];
17424
17425     if (ms > 0 && ms <= 9900) {
17426       /* convert milliseconds to tenths, rounding up */
17427       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17428
17429       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17430       return buf;
17431     }
17432
17433     /* convert milliseconds to seconds, rounding up */
17434     /* use floating point to avoid strangeness of integer division
17435        with negative dividends on many machines */
17436     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17437
17438     if (second < 0) {
17439         sign = "-";
17440         second = -second;
17441     }
17442
17443     day = second / (60 * 60 * 24);
17444     second = second % (60 * 60 * 24);
17445     hour = second / (60 * 60);
17446     second = second % (60 * 60);
17447     minute = second / 60;
17448     second = second % 60;
17449
17450     if (day > 0)
17451       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17452               sign, day, hour, minute, second);
17453     else if (hour > 0)
17454       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17455     else
17456       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17457
17458     return buf;
17459 }
17460
17461
17462 /*
17463  * This is necessary because some C libraries aren't ANSI C compliant yet.
17464  */
17465 char *
17466 StrStr (char *string, char *match)
17467 {
17468     int i, length;
17469
17470     length = strlen(match);
17471
17472     for (i = strlen(string) - length; i >= 0; i--, string++)
17473       if (!strncmp(match, string, length))
17474         return string;
17475
17476     return NULL;
17477 }
17478
17479 char *
17480 StrCaseStr (char *string, char *match)
17481 {
17482     int i, j, length;
17483
17484     length = strlen(match);
17485
17486     for (i = strlen(string) - length; i >= 0; i--, string++) {
17487         for (j = 0; j < length; j++) {
17488             if (ToLower(match[j]) != ToLower(string[j]))
17489               break;
17490         }
17491         if (j == length) return string;
17492     }
17493
17494     return NULL;
17495 }
17496
17497 #ifndef _amigados
17498 int
17499 StrCaseCmp (char *s1, char *s2)
17500 {
17501     char c1, c2;
17502
17503     for (;;) {
17504         c1 = ToLower(*s1++);
17505         c2 = ToLower(*s2++);
17506         if (c1 > c2) return 1;
17507         if (c1 < c2) return -1;
17508         if (c1 == NULLCHAR) return 0;
17509     }
17510 }
17511
17512
17513 int
17514 ToLower (int c)
17515 {
17516     return isupper(c) ? tolower(c) : c;
17517 }
17518
17519
17520 int
17521 ToUpper (int c)
17522 {
17523     return islower(c) ? toupper(c) : c;
17524 }
17525 #endif /* !_amigados    */
17526
17527 char *
17528 StrSave (char *s)
17529 {
17530   char *ret;
17531
17532   if ((ret = (char *) malloc(strlen(s) + 1)))
17533     {
17534       safeStrCpy(ret, s, strlen(s)+1);
17535     }
17536   return ret;
17537 }
17538
17539 char *
17540 StrSavePtr (char *s, char **savePtr)
17541 {
17542     if (*savePtr) {
17543         free(*savePtr);
17544     }
17545     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17546       safeStrCpy(*savePtr, s, strlen(s)+1);
17547     }
17548     return(*savePtr);
17549 }
17550
17551 char *
17552 PGNDate ()
17553 {
17554     time_t clock;
17555     struct tm *tm;
17556     char buf[MSG_SIZ];
17557
17558     clock = time((time_t *)NULL);
17559     tm = localtime(&clock);
17560     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17561             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17562     return StrSave(buf);
17563 }
17564
17565
17566 char *
17567 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17568 {
17569     int i, j, fromX, fromY, toX, toY;
17570     int whiteToPlay;
17571     char buf[MSG_SIZ];
17572     char *p, *q;
17573     int emptycount;
17574     ChessSquare piece;
17575
17576     whiteToPlay = (gameMode == EditPosition) ?
17577       !blackPlaysFirst : (move % 2 == 0);
17578     p = buf;
17579
17580     /* Piece placement data */
17581     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17582         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17583         emptycount = 0;
17584         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17585             if (boards[move][i][j] == EmptySquare) {
17586                 emptycount++;
17587             } else { ChessSquare piece = boards[move][i][j];
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                 if(PieceToChar(piece) == '+') {
17595                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17596                     *p++ = '+';
17597                     piece = (ChessSquare)(DEMOTED piece);
17598                 }
17599                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17600                 if(p[-1] == '~') {
17601                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17602                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
17603                     *p++ = '~';
17604                 }
17605             }
17606         }
17607         if (emptycount > 0) {
17608             if(emptycount<10) /* [HGM] can be >= 10 */
17609                 *p++ = '0' + emptycount;
17610             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17611             emptycount = 0;
17612         }
17613         *p++ = '/';
17614     }
17615     *(p - 1) = ' ';
17616
17617     /* [HGM] print Crazyhouse or Shogi holdings */
17618     if( gameInfo.holdingsWidth ) {
17619         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17620         q = p;
17621         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17622             piece = boards[move][i][BOARD_WIDTH-1];
17623             if( piece != EmptySquare )
17624               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17625                   *p++ = PieceToChar(piece);
17626         }
17627         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17628             piece = boards[move][BOARD_HEIGHT-i-1][0];
17629             if( piece != EmptySquare )
17630               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17631                   *p++ = PieceToChar(piece);
17632         }
17633
17634         if( q == p ) *p++ = '-';
17635         *p++ = ']';
17636         *p++ = ' ';
17637     }
17638
17639     /* Active color */
17640     *p++ = whiteToPlay ? 'w' : 'b';
17641     *p++ = ' ';
17642
17643   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17644     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17645   } else {
17646   if(nrCastlingRights) {
17647      q = p;
17648      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
17649        /* [HGM] write directly from rights */
17650            if(boards[move][CASTLING][2] != NoRights &&
17651               boards[move][CASTLING][0] != NoRights   )
17652                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17653            if(boards[move][CASTLING][2] != NoRights &&
17654               boards[move][CASTLING][1] != NoRights   )
17655                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17656            if(boards[move][CASTLING][5] != NoRights &&
17657               boards[move][CASTLING][3] != NoRights   )
17658                 *p++ = boards[move][CASTLING][3] + AAA;
17659            if(boards[move][CASTLING][5] != NoRights &&
17660               boards[move][CASTLING][4] != NoRights   )
17661                 *p++ = boards[move][CASTLING][4] + AAA;
17662      } else {
17663
17664         /* [HGM] write true castling rights */
17665         if( nrCastlingRights == 6 ) {
17666             int q, k=0;
17667             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17668                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17669             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17670                  boards[move][CASTLING][2] != NoRights  );
17671             if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
17672                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
17673                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17674                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17675                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17676             }
17677             if(q) *p++ = 'Q';
17678             k = 0;
17679             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17680                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17681             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17682                  boards[move][CASTLING][5] != NoRights  );
17683             if(gameInfo.variant == VariantSChess) {
17684                 for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17685                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
17686                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17687                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17688             }
17689             if(q) *p++ = 'q';
17690         }
17691      }
17692      if (q == p) *p++ = '-'; /* No castling rights */
17693      *p++ = ' ';
17694   }
17695
17696   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17697      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17698      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17699     /* En passant target square */
17700     if (move > backwardMostMove) {
17701         fromX = moveList[move - 1][0] - AAA;
17702         fromY = moveList[move - 1][1] - ONE;
17703         toX = moveList[move - 1][2] - AAA;
17704         toY = moveList[move - 1][3] - ONE;
17705         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17706             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17707             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17708             fromX == toX) {
17709             /* 2-square pawn move just happened */
17710             *p++ = toX + AAA;
17711             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17712         } else {
17713             *p++ = '-';
17714         }
17715     } else if(move == backwardMostMove) {
17716         // [HGM] perhaps we should always do it like this, and forget the above?
17717         if((signed char)boards[move][EP_STATUS] >= 0) {
17718             *p++ = boards[move][EP_STATUS] + AAA;
17719             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17720         } else {
17721             *p++ = '-';
17722         }
17723     } else {
17724         *p++ = '-';
17725     }
17726     *p++ = ' ';
17727   }
17728   }
17729
17730     if(moveCounts)
17731     {   int i = 0, j=move;
17732
17733         /* [HGM] find reversible plies */
17734         if (appData.debugMode) { int k;
17735             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17736             for(k=backwardMostMove; k<=forwardMostMove; k++)
17737                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17738
17739         }
17740
17741         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17742         if( j == backwardMostMove ) i += initialRulePlies;
17743         sprintf(p, "%d ", i);
17744         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17745
17746         /* Fullmove number */
17747         sprintf(p, "%d", (move / 2) + 1);
17748     } else *--p = NULLCHAR;
17749
17750     return StrSave(buf);
17751 }
17752
17753 Boolean
17754 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17755 {
17756     int i, j, k, w=0;
17757     char *p, c;
17758     int emptycount, virgin[BOARD_FILES];
17759     ChessSquare piece;
17760
17761     p = fen;
17762
17763     /* Piece placement data */
17764     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17765         j = 0;
17766         for (;;) {
17767             if (*p == '/' || *p == ' ' || *p == '[' ) {
17768                 if(j > w) w = j;
17769                 emptycount = gameInfo.boardWidth - j;
17770                 while (emptycount--)
17771                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17772                 if (*p == '/') p++;
17773                 else if(autoSize) { // we stumbled unexpectedly into end of board
17774                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17775                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17776                     }
17777                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17778                 }
17779                 break;
17780 #if(BOARD_FILES >= 10)
17781             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
17782                 p++; emptycount=10;
17783                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17784                 while (emptycount--)
17785                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17786 #endif
17787             } else if (*p == '*') {
17788                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
17789             } else if (isdigit(*p)) {
17790                 emptycount = *p++ - '0';
17791                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
17792                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
17793                 while (emptycount--)
17794                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17795             } else if (*p == '+' || isalpha(*p)) {
17796                 if (j >= gameInfo.boardWidth) return FALSE;
17797                 if(*p=='+') {
17798                     piece = CharToPiece(*++p);
17799                     if(piece == EmptySquare) return FALSE; /* unknown piece */
17800                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
17801                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
17802                 } else piece = CharToPiece(*p++);
17803
17804                 if(piece==EmptySquare) return FALSE; /* unknown piece */
17805                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
17806                     piece = (ChessSquare) (PROMOTED piece);
17807                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
17808                     p++;
17809                 }
17810                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
17811             } else {
17812                 return FALSE;
17813             }
17814         }
17815     }
17816     while (*p == '/' || *p == ' ') p++;
17817
17818     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
17819
17820     /* [HGM] by default clear Crazyhouse holdings, if present */
17821     if(gameInfo.holdingsWidth) {
17822        for(i=0; i<BOARD_HEIGHT; i++) {
17823            board[i][0]             = EmptySquare; /* black holdings */
17824            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
17825            board[i][1]             = (ChessSquare) 0; /* black counts */
17826            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
17827        }
17828     }
17829
17830     /* [HGM] look for Crazyhouse holdings here */
17831     while(*p==' ') p++;
17832     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
17833         if(*p == '[') p++;
17834         if(*p == '-' ) p++; /* empty holdings */ else {
17835             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
17836             /* if we would allow FEN reading to set board size, we would   */
17837             /* have to add holdings and shift the board read so far here   */
17838             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
17839                 p++;
17840                 if((int) piece >= (int) BlackPawn ) {
17841                     i = (int)piece - (int)BlackPawn;
17842                     i = PieceToNumber((ChessSquare)i);
17843                     if( i >= gameInfo.holdingsSize ) return FALSE;
17844                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
17845                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
17846                 } else {
17847                     i = (int)piece - (int)WhitePawn;
17848                     i = PieceToNumber((ChessSquare)i);
17849                     if( i >= gameInfo.holdingsSize ) return FALSE;
17850                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
17851                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
17852                 }
17853             }
17854         }
17855         if(*p == ']') p++;
17856     }
17857
17858     while(*p == ' ') p++;
17859
17860     /* Active color */
17861     c = *p++;
17862     if(appData.colorNickNames) {
17863       if( c == appData.colorNickNames[0] ) c = 'w'; else
17864       if( c == appData.colorNickNames[1] ) c = 'b';
17865     }
17866     switch (c) {
17867       case 'w':
17868         *blackPlaysFirst = FALSE;
17869         break;
17870       case 'b':
17871         *blackPlaysFirst = TRUE;
17872         break;
17873       default:
17874         return FALSE;
17875     }
17876
17877     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
17878     /* return the extra info in global variiables             */
17879
17880     /* set defaults in case FEN is incomplete */
17881     board[EP_STATUS] = EP_UNKNOWN;
17882     for(i=0; i<nrCastlingRights; i++ ) {
17883         board[CASTLING][i] =
17884             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
17885     }   /* assume possible unless obviously impossible */
17886     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
17887     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
17888     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
17889                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
17890     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
17891     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
17892     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
17893                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
17894     FENrulePlies = 0;
17895
17896     while(*p==' ') p++;
17897     if(nrCastlingRights) {
17898       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
17899       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
17900           /* castling indicator present, so default becomes no castlings */
17901           for(i=0; i<nrCastlingRights; i++ ) {
17902                  board[CASTLING][i] = NoRights;
17903           }
17904       }
17905       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
17906              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
17907              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
17908              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
17909         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
17910
17911         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
17912             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
17913             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
17914         }
17915         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
17916             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
17917         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
17918                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
17919         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
17920                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
17921         switch(c) {
17922           case'K':
17923               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
17924               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
17925               board[CASTLING][2] = whiteKingFile;
17926               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
17927               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17928               break;
17929           case'Q':
17930               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
17931               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
17932               board[CASTLING][2] = whiteKingFile;
17933               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
17934               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
17935               break;
17936           case'k':
17937               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
17938               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
17939               board[CASTLING][5] = blackKingFile;
17940               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
17941               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17942               break;
17943           case'q':
17944               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
17945               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
17946               board[CASTLING][5] = blackKingFile;
17947               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
17948               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
17949           case '-':
17950               break;
17951           default: /* FRC castlings */
17952               if(c >= 'a') { /* black rights */
17953                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
17954                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17955                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
17956                   if(i == BOARD_RGHT) break;
17957                   board[CASTLING][5] = i;
17958                   c -= AAA;
17959                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
17960                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
17961                   if(c > i)
17962                       board[CASTLING][3] = c;
17963                   else
17964                       board[CASTLING][4] = c;
17965               } else { /* white rights */
17966                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
17967                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
17968                     if(board[0][i] == WhiteKing) break;
17969                   if(i == BOARD_RGHT) break;
17970                   board[CASTLING][2] = i;
17971                   c -= AAA - 'a' + 'A';
17972                   if(board[0][c] >= WhiteKing) break;
17973                   if(c > i)
17974                       board[CASTLING][0] = c;
17975                   else
17976                       board[CASTLING][1] = c;
17977               }
17978         }
17979       }
17980       for(i=0; i<nrCastlingRights; i++)
17981         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
17982       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
17983     if (appData.debugMode) {
17984         fprintf(debugFP, "FEN castling rights:");
17985         for(i=0; i<nrCastlingRights; i++)
17986         fprintf(debugFP, " %d", board[CASTLING][i]);
17987         fprintf(debugFP, "\n");
17988     }
17989
17990       while(*p==' ') p++;
17991     }
17992
17993     /* read e.p. field in games that know e.p. capture */
17994     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17995        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17996        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
17997       if(*p=='-') {
17998         p++; board[EP_STATUS] = EP_NONE;
17999       } else {
18000          char c = *p++ - AAA;
18001
18002          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18003          if(*p >= '0' && *p <='9') p++;
18004          board[EP_STATUS] = c;
18005       }
18006     }
18007
18008
18009     if(sscanf(p, "%d", &i) == 1) {
18010         FENrulePlies = i; /* 50-move ply counter */
18011         /* (The move number is still ignored)    */
18012     }
18013
18014     return TRUE;
18015 }
18016
18017 void
18018 EditPositionPasteFEN (char *fen)
18019 {
18020   if (fen != NULL) {
18021     Board initial_position;
18022
18023     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18024       DisplayError(_("Bad FEN position in clipboard"), 0);
18025       return ;
18026     } else {
18027       int savedBlackPlaysFirst = blackPlaysFirst;
18028       EditPositionEvent();
18029       blackPlaysFirst = savedBlackPlaysFirst;
18030       CopyBoard(boards[0], initial_position);
18031       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18032       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18033       DisplayBothClocks();
18034       DrawPosition(FALSE, boards[currentMove]);
18035     }
18036   }
18037 }
18038
18039 static char cseq[12] = "\\   ";
18040
18041 Boolean
18042 set_cont_sequence (char *new_seq)
18043 {
18044     int len;
18045     Boolean ret;
18046
18047     // handle bad attempts to set the sequence
18048         if (!new_seq)
18049                 return 0; // acceptable error - no debug
18050
18051     len = strlen(new_seq);
18052     ret = (len > 0) && (len < sizeof(cseq));
18053     if (ret)
18054       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18055     else if (appData.debugMode)
18056       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18057     return ret;
18058 }
18059
18060 /*
18061     reformat a source message so words don't cross the width boundary.  internal
18062     newlines are not removed.  returns the wrapped size (no null character unless
18063     included in source message).  If dest is NULL, only calculate the size required
18064     for the dest buffer.  lp argument indicats line position upon entry, and it's
18065     passed back upon exit.
18066 */
18067 int
18068 wrap (char *dest, char *src, int count, int width, int *lp)
18069 {
18070     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18071
18072     cseq_len = strlen(cseq);
18073     old_line = line = *lp;
18074     ansi = len = clen = 0;
18075
18076     for (i=0; i < count; i++)
18077     {
18078         if (src[i] == '\033')
18079             ansi = 1;
18080
18081         // if we hit the width, back up
18082         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18083         {
18084             // store i & len in case the word is too long
18085             old_i = i, old_len = len;
18086
18087             // find the end of the last word
18088             while (i && src[i] != ' ' && src[i] != '\n')
18089             {
18090                 i--;
18091                 len--;
18092             }
18093
18094             // word too long?  restore i & len before splitting it
18095             if ((old_i-i+clen) >= width)
18096             {
18097                 i = old_i;
18098                 len = old_len;
18099             }
18100
18101             // extra space?
18102             if (i && src[i-1] == ' ')
18103                 len--;
18104
18105             if (src[i] != ' ' && src[i] != '\n')
18106             {
18107                 i--;
18108                 if (len)
18109                     len--;
18110             }
18111
18112             // now append the newline and continuation sequence
18113             if (dest)
18114                 dest[len] = '\n';
18115             len++;
18116             if (dest)
18117                 strncpy(dest+len, cseq, cseq_len);
18118             len += cseq_len;
18119             line = cseq_len;
18120             clen = cseq_len;
18121             continue;
18122         }
18123
18124         if (dest)
18125             dest[len] = src[i];
18126         len++;
18127         if (!ansi)
18128             line++;
18129         if (src[i] == '\n')
18130             line = 0;
18131         if (src[i] == 'm')
18132             ansi = 0;
18133     }
18134     if (dest && appData.debugMode)
18135     {
18136         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18137             count, width, line, len, *lp);
18138         show_bytes(debugFP, src, count);
18139         fprintf(debugFP, "\ndest: ");
18140         show_bytes(debugFP, dest, len);
18141         fprintf(debugFP, "\n");
18142     }
18143     *lp = dest ? line : old_line;
18144
18145     return len;
18146 }
18147
18148 // [HGM] vari: routines for shelving variations
18149 Boolean modeRestore = FALSE;
18150
18151 void
18152 PushInner (int firstMove, int lastMove)
18153 {
18154         int i, j, nrMoves = lastMove - firstMove;
18155
18156         // push current tail of game on stack
18157         savedResult[storedGames] = gameInfo.result;
18158         savedDetails[storedGames] = gameInfo.resultDetails;
18159         gameInfo.resultDetails = NULL;
18160         savedFirst[storedGames] = firstMove;
18161         savedLast [storedGames] = lastMove;
18162         savedFramePtr[storedGames] = framePtr;
18163         framePtr -= nrMoves; // reserve space for the boards
18164         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18165             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18166             for(j=0; j<MOVE_LEN; j++)
18167                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18168             for(j=0; j<2*MOVE_LEN; j++)
18169                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18170             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18171             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18172             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18173             pvInfoList[firstMove+i-1].depth = 0;
18174             commentList[framePtr+i] = commentList[firstMove+i];
18175             commentList[firstMove+i] = NULL;
18176         }
18177
18178         storedGames++;
18179         forwardMostMove = firstMove; // truncate game so we can start variation
18180 }
18181
18182 void
18183 PushTail (int firstMove, int lastMove)
18184 {
18185         if(appData.icsActive) { // only in local mode
18186                 forwardMostMove = currentMove; // mimic old ICS behavior
18187                 return;
18188         }
18189         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18190
18191         PushInner(firstMove, lastMove);
18192         if(storedGames == 1) GreyRevert(FALSE);
18193         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18194 }
18195
18196 void
18197 PopInner (Boolean annotate)
18198 {
18199         int i, j, nrMoves;
18200         char buf[8000], moveBuf[20];
18201
18202         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18203         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18204         nrMoves = savedLast[storedGames] - currentMove;
18205         if(annotate) {
18206                 int cnt = 10;
18207                 if(!WhiteOnMove(currentMove))
18208                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18209                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18210                 for(i=currentMove; i<forwardMostMove; i++) {
18211                         if(WhiteOnMove(i))
18212                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18213                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18214                         strcat(buf, moveBuf);
18215                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18216                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18217                 }
18218                 strcat(buf, ")");
18219         }
18220         for(i=1; i<=nrMoves; i++) { // copy last variation back
18221             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18222             for(j=0; j<MOVE_LEN; j++)
18223                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18224             for(j=0; j<2*MOVE_LEN; j++)
18225                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18226             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18227             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18228             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18229             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18230             commentList[currentMove+i] = commentList[framePtr+i];
18231             commentList[framePtr+i] = NULL;
18232         }
18233         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18234         framePtr = savedFramePtr[storedGames];
18235         gameInfo.result = savedResult[storedGames];
18236         if(gameInfo.resultDetails != NULL) {
18237             free(gameInfo.resultDetails);
18238       }
18239         gameInfo.resultDetails = savedDetails[storedGames];
18240         forwardMostMove = currentMove + nrMoves;
18241 }
18242
18243 Boolean
18244 PopTail (Boolean annotate)
18245 {
18246         if(appData.icsActive) return FALSE; // only in local mode
18247         if(!storedGames) return FALSE; // sanity
18248         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18249
18250         PopInner(annotate);
18251         if(currentMove < forwardMostMove) ForwardEvent(); else
18252         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18253
18254         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18255         return TRUE;
18256 }
18257
18258 void
18259 CleanupTail ()
18260 {       // remove all shelved variations
18261         int i;
18262         for(i=0; i<storedGames; i++) {
18263             if(savedDetails[i])
18264                 free(savedDetails[i]);
18265             savedDetails[i] = NULL;
18266         }
18267         for(i=framePtr; i<MAX_MOVES; i++) {
18268                 if(commentList[i]) free(commentList[i]);
18269                 commentList[i] = NULL;
18270         }
18271         framePtr = MAX_MOVES-1;
18272         storedGames = 0;
18273 }
18274
18275 void
18276 LoadVariation (int index, char *text)
18277 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18278         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18279         int level = 0, move;
18280
18281         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18282         // first find outermost bracketing variation
18283         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18284             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18285                 if(*p == '{') wait = '}'; else
18286                 if(*p == '[') wait = ']'; else
18287                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18288                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18289             }
18290             if(*p == wait) wait = NULLCHAR; // closing ]} found
18291             p++;
18292         }
18293         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18294         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18295         end[1] = NULLCHAR; // clip off comment beyond variation
18296         ToNrEvent(currentMove-1);
18297         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18298         // kludge: use ParsePV() to append variation to game
18299         move = currentMove;
18300         ParsePV(start, TRUE, TRUE);
18301         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18302         ClearPremoveHighlights();
18303         CommentPopDown();
18304         ToNrEvent(currentMove+1);
18305 }
18306
18307 void
18308 LoadTheme ()
18309 {
18310     char *p, *q, buf[MSG_SIZ];
18311     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18312         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18313         ParseArgsFromString(buf);
18314         ActivateTheme(TRUE); // also redo colors
18315         return;
18316     }
18317     p = nickName;
18318     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18319     {
18320         int len;
18321         q = appData.themeNames;
18322         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18323       if(appData.useBitmaps) {
18324         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18325                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18326                 appData.liteBackTextureMode,
18327                 appData.darkBackTextureMode );
18328       } else {
18329         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18330                 Col2Text(2),   // lightSquareColor
18331                 Col2Text(3) ); // darkSquareColor
18332       }
18333       if(appData.useBorder) {
18334         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18335                 appData.border);
18336       } else {
18337         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18338       }
18339       if(appData.useFont) {
18340         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18341                 appData.renderPiecesWithFont,
18342                 appData.fontToPieceTable,
18343                 Col2Text(9),    // appData.fontBackColorWhite
18344                 Col2Text(10) ); // appData.fontForeColorBlack
18345       } else {
18346         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18347                 appData.pieceDirectory);
18348         if(!appData.pieceDirectory[0])
18349           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18350                 Col2Text(0),   // whitePieceColor
18351                 Col2Text(1) ); // blackPieceColor
18352       }
18353       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18354                 Col2Text(4),   // highlightSquareColor
18355                 Col2Text(5) ); // premoveHighlightColor
18356         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18357         if(insert != q) insert[-1] = NULLCHAR;
18358         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18359         if(q)   free(q);
18360     }
18361     ActivateTheme(FALSE);
18362 }