Fix illegal drops
[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 "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299
300 /* States for ics_getting_history */
301 #define H_FALSE 0
302 #define H_REQUESTED 1
303 #define H_GOT_REQ_HEADER 2
304 #define H_GOT_UNREQ_HEADER 3
305 #define H_GETTING_MOVES 4
306 #define H_GOT_UNWANTED_HEADER 5
307
308 /* whosays values for GameEnds */
309 #define GE_ICS 0
310 #define GE_ENGINE 1
311 #define GE_PLAYER 2
312 #define GE_FILE 3
313 #define GE_XBOARD 4
314 #define GE_ENGINE1 5
315 #define GE_ENGINE2 6
316
317 /* Maximum number of games in a cmail message */
318 #define CMAIL_MAX_GAMES 20
319
320 /* Different types of move when calling RegisterMove */
321 #define CMAIL_MOVE   0
322 #define CMAIL_RESIGN 1
323 #define CMAIL_DRAW   2
324 #define CMAIL_ACCEPT 3
325
326 /* Different types of result to remember for each game */
327 #define CMAIL_NOT_RESULT 0
328 #define CMAIL_OLD_RESULT 1
329 #define CMAIL_NEW_RESULT 2
330
331 /* Telnet protocol constants */
332 #define TN_WILL 0373
333 #define TN_WONT 0374
334 #define TN_DO   0375
335 #define TN_DONT 0376
336 #define TN_IAC  0377
337 #define TN_ECHO 0001
338 #define TN_SGA  0003
339 #define TN_PORT 23
340
341 char*
342 safeStrCpy (char *dst, const char *src, size_t count)
343 { // [HGM] made safe
344   int i;
345   assert( dst != NULL );
346   assert( src != NULL );
347   assert( count > 0 );
348
349   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
350   if(  i == count && dst[count-1] != NULLCHAR)
351     {
352       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
353       if(appData.debugMode)
354         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
355     }
356
357   return dst;
358 }
359
360 /* Some compiler can't cast u64 to double
361  * This function do the job for us:
362
363  * We use the highest bit for cast, this only
364  * works if the highest bit is not
365  * in use (This should not happen)
366  *
367  * We used this for all compiler
368  */
369 double
370 u64ToDouble (u64 value)
371 {
372   double r;
373   u64 tmp = value & u64Const(0x7fffffffffffffff);
374   r = (double)(s64)tmp;
375   if (value & u64Const(0x8000000000000000))
376        r +=  9.2233720368547758080e18; /* 2^63 */
377  return r;
378 }
379
380 /* Fake up flags for now, as we aren't keeping track of castling
381    availability yet. [HGM] Change of logic: the flag now only
382    indicates the type of castlings allowed by the rule of the game.
383    The actual rights themselves are maintained in the array
384    castlingRights, as part of the game history, and are not probed
385    by this function.
386  */
387 int
388 PosFlags (index)
389 {
390   int flags = F_ALL_CASTLE_OK;
391   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
392   switch (gameInfo.variant) {
393   case VariantSuicide:
394     flags &= ~F_ALL_CASTLE_OK;
395   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
396     flags |= F_IGNORE_CHECK;
397   case VariantLosers:
398     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
399     break;
400   case VariantAtomic:
401     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
402     break;
403   case VariantKriegspiel:
404     flags |= F_KRIEGSPIEL_CAPTURE;
405     break;
406   case VariantCapaRandom:
407   case VariantFischeRandom:
408     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
409   case VariantNoCastle:
410   case VariantShatranj:
411   case VariantCourier:
412   case VariantMakruk:
413   case VariantASEAN:
414   case VariantGrand:
415     flags &= ~F_ALL_CASTLE_OK;
416     break;
417   case VariantChu:
418   case VariantChuChess:
419   case VariantLion:
420     flags |= F_NULL_MOVE;
421     break;
422   default:
423     break;
424   }
425   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
426   return flags;
427 }
428
429 FILE *gameFileFP, *debugFP, *serverFP;
430 char *currentDebugFile; // [HGM] debug split: to remember name
431
432 /*
433     [AS] Note: sometimes, the sscanf() function is used to parse the input
434     into a fixed-size buffer. Because of this, we must be prepared to
435     receive strings as long as the size of the input buffer, which is currently
436     set to 4K for Windows and 8K for the rest.
437     So, we must either allocate sufficiently large buffers here, or
438     reduce the size of the input buffer in the input reading part.
439 */
440
441 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
442 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
443 char thinkOutput1[MSG_SIZ*10];
444
445 ChessProgramState first, second, pairing;
446
447 /* premove variables */
448 int premoveToX = 0;
449 int premoveToY = 0;
450 int premoveFromX = 0;
451 int premoveFromY = 0;
452 int premovePromoChar = 0;
453 int gotPremove = 0;
454 Boolean alarmSounded;
455 /* end premove variables */
456
457 char *ics_prefix = "$";
458 enum ICS_TYPE ics_type = ICS_GENERIC;
459
460 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
461 int pauseExamForwardMostMove = 0;
462 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
463 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
464 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
465 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
466 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
467 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
468 int whiteFlag = FALSE, blackFlag = FALSE;
469 int userOfferedDraw = FALSE;
470 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
471 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
472 int cmailMoveType[CMAIL_MAX_GAMES];
473 long ics_clock_paused = 0;
474 ProcRef icsPR = NoProc, cmailPR = NoProc;
475 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
476 GameMode gameMode = BeginningOfGame;
477 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
478 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
479 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
480 int hiddenThinkOutputState = 0; /* [AS] */
481 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
482 int adjudicateLossPlies = 6;
483 char white_holding[64], black_holding[64];
484 TimeMark lastNodeCountTime;
485 long lastNodeCount=0;
486 int shiftKey, controlKey; // [HGM] set by mouse handler
487
488 int have_sent_ICS_logon = 0;
489 int movesPerSession;
490 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
491 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
492 Boolean adjustedClock;
493 long timeControl_2; /* [AS] Allow separate time controls */
494 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
495 long timeRemaining[2][MAX_MOVES];
496 int matchGame = 0, nextGame = 0, roundNr = 0;
497 Boolean waitingForGame = FALSE, startingEngine = FALSE;
498 TimeMark programStartTime, pauseStart;
499 char ics_handle[MSG_SIZ];
500 int have_set_title = 0;
501
502 /* animateTraining preserves the state of appData.animate
503  * when Training mode is activated. This allows the
504  * response to be animated when appData.animate == TRUE and
505  * appData.animateDragging == TRUE.
506  */
507 Boolean animateTraining;
508
509 GameInfo gameInfo;
510
511 AppData appData;
512
513 Board boards[MAX_MOVES];
514 /* [HGM] Following 7 needed for accurate legality tests: */
515 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
516 signed char  initialRights[BOARD_FILES];
517 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
518 int   initialRulePlies, FENrulePlies;
519 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
520 int loadFlag = 0;
521 Boolean shuffleOpenings;
522 int mute; // mute all sounds
523
524 // [HGM] vari: next 12 to save and restore variations
525 #define MAX_VARIATIONS 10
526 int framePtr = MAX_MOVES-1; // points to free stack entry
527 int storedGames = 0;
528 int savedFirst[MAX_VARIATIONS];
529 int savedLast[MAX_VARIATIONS];
530 int savedFramePtr[MAX_VARIATIONS];
531 char *savedDetails[MAX_VARIATIONS];
532 ChessMove savedResult[MAX_VARIATIONS];
533
534 void PushTail P((int firstMove, int lastMove));
535 Boolean PopTail P((Boolean annotate));
536 void PushInner P((int firstMove, int lastMove));
537 void PopInner P((Boolean annotate));
538 void CleanupTail P((void));
539
540 ChessSquare  FIDEArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
544         BlackKing, BlackBishop, BlackKnight, BlackRook }
545 };
546
547 ChessSquare twoKingsArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
549         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
551         BlackKing, BlackKing, BlackKnight, BlackRook }
552 };
553
554 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
556         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
557     { BlackRook, BlackMan, BlackBishop, BlackQueen,
558         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
559 };
560
561 ChessSquare SpartanArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
563         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
564     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
565         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
566 };
567
568 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
569     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
570         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
571     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
572         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
573 };
574
575 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
576     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
577         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
579         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 };
581
582 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
583     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
584         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
585     { BlackRook, BlackKnight, BlackMan, BlackFerz,
586         BlackKing, BlackMan, BlackKnight, BlackRook }
587 };
588
589 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
590     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
591         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
592     { BlackRook, BlackKnight, BlackMan, BlackFerz,
593         BlackKing, BlackMan, BlackKnight, BlackRook }
594 };
595
596 ChessSquare  lionArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
598         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackLion, BlackBishop, BlackQueen,
600         BlackKing, BlackBishop, BlackKnight, BlackRook }
601 };
602
603
604 #if (BOARD_FILES>=10)
605 ChessSquare ShogiArray[2][BOARD_FILES] = {
606     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
607         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
608     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
609         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
610 };
611
612 ChessSquare XiangqiArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
614         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
616         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
617 };
618
619 ChessSquare CapablancaArray[2][BOARD_FILES] = {
620     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
621         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
622     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
623         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
624 };
625
626 ChessSquare GreatArray[2][BOARD_FILES] = {
627     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
628         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
629     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
630         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
631 };
632
633 ChessSquare JanusArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
635         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
636     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
637         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
638 };
639
640 ChessSquare GrandArray[2][BOARD_FILES] = {
641     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
642         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
643     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
644         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
645 };
646
647 ChessSquare ChuChessArray[2][BOARD_FILES] = {
648     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
649         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
650     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
651         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
652 };
653
654 #ifdef GOTHIC
655 ChessSquare GothicArray[2][BOARD_FILES] = {
656     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
657         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
658     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
659         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
660 };
661 #else // !GOTHIC
662 #define GothicArray CapablancaArray
663 #endif // !GOTHIC
664
665 #ifdef FALCON
666 ChessSquare FalconArray[2][BOARD_FILES] = {
667     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
668         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
669     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
670         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
671 };
672 #else // !FALCON
673 #define FalconArray CapablancaArray
674 #endif // !FALCON
675
676 #else // !(BOARD_FILES>=10)
677 #define XiangqiPosition FIDEArray
678 #define CapablancaArray FIDEArray
679 #define GothicArray FIDEArray
680 #define GreatArray FIDEArray
681 #endif // !(BOARD_FILES>=10)
682
683 #if (BOARD_FILES>=12)
684 ChessSquare CourierArray[2][BOARD_FILES] = {
685     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
686         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
687     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
688         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
689 };
690 ChessSquare ChuArray[6][BOARD_FILES] = {
691     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
692       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
693     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
694       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
695     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
696       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
697     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
698       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
699     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
700       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
701     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
702       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
703 };
704 #else // !(BOARD_FILES>=12)
705 #define CourierArray CapablancaArray
706 #define ChuArray CapablancaArray
707 #endif // !(BOARD_FILES>=12)
708
709
710 Board initialPosition;
711
712
713 /* Convert str to a rating. Checks for special cases of "----",
714
715    "++++", etc. Also strips ()'s */
716 int
717 string_to_rating (char *str)
718 {
719   while(*str && !isdigit(*str)) ++str;
720   if (!*str)
721     return 0;   /* One of the special "no rating" cases */
722   else
723     return atoi(str);
724 }
725
726 void
727 ClearProgramStats ()
728 {
729     /* Init programStats */
730     programStats.movelist[0] = 0;
731     programStats.depth = 0;
732     programStats.nr_moves = 0;
733     programStats.moves_left = 0;
734     programStats.nodes = 0;
735     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
736     programStats.score = 0;
737     programStats.got_only_move = 0;
738     programStats.got_fail = 0;
739     programStats.line_is_book = 0;
740 }
741
742 void
743 CommonEngineInit ()
744 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
745     if (appData.firstPlaysBlack) {
746         first.twoMachinesColor = "black\n";
747         second.twoMachinesColor = "white\n";
748     } else {
749         first.twoMachinesColor = "white\n";
750         second.twoMachinesColor = "black\n";
751     }
752
753     first.other = &second;
754     second.other = &first;
755
756     { float norm = 1;
757         if(appData.timeOddsMode) {
758             norm = appData.timeOdds[0];
759             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
760         }
761         first.timeOdds  = appData.timeOdds[0]/norm;
762         second.timeOdds = appData.timeOdds[1]/norm;
763     }
764
765     if(programVersion) free(programVersion);
766     if (appData.noChessProgram) {
767         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
768         sprintf(programVersion, "%s", PACKAGE_STRING);
769     } else {
770       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
771       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
772       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
773     }
774 }
775
776 void
777 UnloadEngine (ChessProgramState *cps)
778 {
779         /* Kill off first chess program */
780         if (cps->isr != NULL)
781           RemoveInputSource(cps->isr);
782         cps->isr = NULL;
783
784         if (cps->pr != NoProc) {
785             ExitAnalyzeMode();
786             DoSleep( appData.delayBeforeQuit );
787             SendToProgram("quit\n", cps);
788             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
789         }
790         cps->pr = NoProc;
791         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
792 }
793
794 void
795 ClearOptions (ChessProgramState *cps)
796 {
797     int i;
798     cps->nrOptions = cps->comboCnt = 0;
799     for(i=0; i<MAX_OPTIONS; i++) {
800         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
801         cps->option[i].textValue = 0;
802     }
803 }
804
805 char *engineNames[] = {
806   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
807      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
808 N_("first"),
809   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("second")
812 };
813
814 void
815 InitEngine (ChessProgramState *cps, int n)
816 {   // [HGM] all engine initialiation put in a function that does one engine
817
818     ClearOptions(cps);
819
820     cps->which = engineNames[n];
821     cps->maybeThinking = FALSE;
822     cps->pr = NoProc;
823     cps->isr = NULL;
824     cps->sendTime = 2;
825     cps->sendDrawOffers = 1;
826
827     cps->program = appData.chessProgram[n];
828     cps->host = appData.host[n];
829     cps->dir = appData.directory[n];
830     cps->initString = appData.engInitString[n];
831     cps->computerString = appData.computerString[n];
832     cps->useSigint  = TRUE;
833     cps->useSigterm = TRUE;
834     cps->reuse = appData.reuse[n];
835     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
836     cps->useSetboard = FALSE;
837     cps->useSAN = FALSE;
838     cps->usePing = FALSE;
839     cps->lastPing = 0;
840     cps->lastPong = 0;
841     cps->usePlayother = FALSE;
842     cps->useColors = TRUE;
843     cps->useUsermove = FALSE;
844     cps->sendICS = FALSE;
845     cps->sendName = appData.icsActive;
846     cps->sdKludge = FALSE;
847     cps->stKludge = FALSE;
848     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
849     TidyProgramName(cps->program, cps->host, cps->tidy);
850     cps->matchWins = 0;
851     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
852     cps->analysisSupport = 2; /* detect */
853     cps->analyzing = FALSE;
854     cps->initDone = FALSE;
855     cps->reload = FALSE;
856     cps->pseudo = appData.pseudo[n];
857
858     /* New features added by Tord: */
859     cps->useFEN960 = FALSE;
860     cps->useOOCastle = TRUE;
861     /* End of new features added by Tord. */
862     cps->fenOverride  = appData.fenOverride[n];
863
864     /* [HGM] time odds: set factor for each machine */
865     cps->timeOdds  = appData.timeOdds[n];
866
867     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
868     cps->accumulateTC = appData.accumulateTC[n];
869     cps->maxNrOfSessions = 1;
870
871     /* [HGM] debug */
872     cps->debug = FALSE;
873
874     cps->drawDepth = appData.drawDepth[n];
875     cps->supportsNPS = UNKNOWN;
876     cps->memSize = FALSE;
877     cps->maxCores = FALSE;
878     ASSIGN(cps->egtFormats, "");
879
880     /* [HGM] options */
881     cps->optionSettings  = appData.engOptions[n];
882
883     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
884     cps->isUCI = appData.isUCI[n]; /* [AS] */
885     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
886     cps->highlight = 0;
887
888     if (appData.protocolVersion[n] > PROTOVER
889         || appData.protocolVersion[n] < 1)
890       {
891         char buf[MSG_SIZ];
892         int len;
893
894         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
895                        appData.protocolVersion[n]);
896         if( (len >= MSG_SIZ) && appData.debugMode )
897           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
898
899         DisplayFatalError(buf, 0, 2);
900       }
901     else
902       {
903         cps->protocolVersion = appData.protocolVersion[n];
904       }
905
906     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
907     ParseFeatures(appData.featureDefaults, cps);
908 }
909
910 ChessProgramState *savCps;
911
912 GameMode oldMode;
913
914 void
915 LoadEngine ()
916 {
917     int i;
918     if(WaitForEngine(savCps, LoadEngine)) return;
919     CommonEngineInit(); // recalculate time odds
920     if(gameInfo.variant != StringToVariant(appData.variant)) {
921         // we changed variant when loading the engine; this forces us to reset
922         Reset(TRUE, savCps != &first);
923         oldMode = BeginningOfGame; // to prevent restoring old mode
924     }
925     InitChessProgram(savCps, FALSE);
926     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
927     DisplayMessage("", "");
928     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
929     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
930     ThawUI();
931     SetGNUMode();
932     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
933 }
934
935 void
936 ReplaceEngine (ChessProgramState *cps, int n)
937 {
938     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
939     keepInfo = 1;
940     if(oldMode != BeginningOfGame) EditGameEvent();
941     keepInfo = 0;
942     UnloadEngine(cps);
943     appData.noChessProgram = FALSE;
944     appData.clockMode = TRUE;
945     InitEngine(cps, n);
946     UpdateLogos(TRUE);
947     if(n) return; // only startup first engine immediately; second can wait
948     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
949     LoadEngine();
950 }
951
952 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
953 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
954
955 static char resetOptions[] =
956         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
957         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
958         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
959         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
960
961 void
962 FloatToFront(char **list, char *engineLine)
963 {
964     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
965     int i=0;
966     if(appData.recentEngines <= 0) return;
967     TidyProgramName(engineLine, "localhost", tidy+1);
968     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
969     strncpy(buf+1, *list, MSG_SIZ-50);
970     if(p = strstr(buf, tidy)) { // tidy name appears in list
971         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
972         while(*p++ = *++q); // squeeze out
973     }
974     strcat(tidy, buf+1); // put list behind tidy name
975     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
976     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
977     ASSIGN(*list, tidy+1);
978 }
979
980 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
981
982 void
983 Load (ChessProgramState *cps, int i)
984 {
985     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
986     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
987         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
988         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
989         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
990         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
991         appData.firstProtocolVersion = PROTOVER;
992         ParseArgsFromString(buf);
993         SwapEngines(i);
994         ReplaceEngine(cps, i);
995         FloatToFront(&appData.recentEngineList, engineLine);
996         return;
997     }
998     p = engineName;
999     while(q = strchr(p, SLASH)) p = q+1;
1000     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1001     if(engineDir[0] != NULLCHAR) {
1002         ASSIGN(appData.directory[i], engineDir); p = engineName;
1003     } else if(p != engineName) { // derive directory from engine path, when not given
1004         p[-1] = 0;
1005         ASSIGN(appData.directory[i], engineName);
1006         p[-1] = SLASH;
1007         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1008     } else { ASSIGN(appData.directory[i], "."); }
1009     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1010     if(params[0]) {
1011         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1012         snprintf(command, MSG_SIZ, "%s %s", p, params);
1013         p = command;
1014     }
1015     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1016     ASSIGN(appData.chessProgram[i], p);
1017     appData.isUCI[i] = isUCI;
1018     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1019     appData.hasOwnBookUCI[i] = hasBook;
1020     if(!nickName[0]) useNick = FALSE;
1021     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1022     if(addToList) {
1023         int len;
1024         char quote;
1025         q = firstChessProgramNames;
1026         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1027         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1028         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1029                         quote, p, quote, appData.directory[i],
1030                         useNick ? " -fn \"" : "",
1031                         useNick ? nickName : "",
1032                         useNick ? "\"" : "",
1033                         v1 ? " -firstProtocolVersion 1" : "",
1034                         hasBook ? "" : " -fNoOwnBookUCI",
1035                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1036                         storeVariant ? " -variant " : "",
1037                         storeVariant ? VariantName(gameInfo.variant) : "");
1038         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1039         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1040         if(insert != q) insert[-1] = NULLCHAR;
1041         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1042         if(q)   free(q);
1043         FloatToFront(&appData.recentEngineList, buf);
1044     }
1045     ReplaceEngine(cps, i);
1046 }
1047
1048 void
1049 InitTimeControls ()
1050 {
1051     int matched, min, sec;
1052     /*
1053      * Parse timeControl resource
1054      */
1055     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1056                           appData.movesPerSession)) {
1057         char buf[MSG_SIZ];
1058         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1059         DisplayFatalError(buf, 0, 2);
1060     }
1061
1062     /*
1063      * Parse searchTime resource
1064      */
1065     if (*appData.searchTime != NULLCHAR) {
1066         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1067         if (matched == 1) {
1068             searchTime = min * 60;
1069         } else if (matched == 2) {
1070             searchTime = min * 60 + sec;
1071         } else {
1072             char buf[MSG_SIZ];
1073             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1074             DisplayFatalError(buf, 0, 2);
1075         }
1076     }
1077 }
1078
1079 void
1080 InitBackEnd1 ()
1081 {
1082
1083     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1084     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1085
1086     GetTimeMark(&programStartTime);
1087     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1088     appData.seedBase = random() + (random()<<15);
1089     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1090
1091     ClearProgramStats();
1092     programStats.ok_to_send = 1;
1093     programStats.seen_stat = 0;
1094
1095     /*
1096      * Initialize game list
1097      */
1098     ListNew(&gameList);
1099
1100
1101     /*
1102      * Internet chess server status
1103      */
1104     if (appData.icsActive) {
1105         appData.matchMode = FALSE;
1106         appData.matchGames = 0;
1107 #if ZIPPY
1108         appData.noChessProgram = !appData.zippyPlay;
1109 #else
1110         appData.zippyPlay = FALSE;
1111         appData.zippyTalk = FALSE;
1112         appData.noChessProgram = TRUE;
1113 #endif
1114         if (*appData.icsHelper != NULLCHAR) {
1115             appData.useTelnet = TRUE;
1116             appData.telnetProgram = appData.icsHelper;
1117         }
1118     } else {
1119         appData.zippyTalk = appData.zippyPlay = FALSE;
1120     }
1121
1122     /* [AS] Initialize pv info list [HGM] and game state */
1123     {
1124         int i, j;
1125
1126         for( i=0; i<=framePtr; i++ ) {
1127             pvInfoList[i].depth = -1;
1128             boards[i][EP_STATUS] = EP_NONE;
1129             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1130         }
1131     }
1132
1133     InitTimeControls();
1134
1135     /* [AS] Adjudication threshold */
1136     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1137
1138     InitEngine(&first, 0);
1139     InitEngine(&second, 1);
1140     CommonEngineInit();
1141
1142     pairing.which = "pairing"; // pairing engine
1143     pairing.pr = NoProc;
1144     pairing.isr = NULL;
1145     pairing.program = appData.pairingEngine;
1146     pairing.host = "localhost";
1147     pairing.dir = ".";
1148
1149     if (appData.icsActive) {
1150         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1151     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1152         appData.clockMode = FALSE;
1153         first.sendTime = second.sendTime = 0;
1154     }
1155
1156 #if ZIPPY
1157     /* Override some settings from environment variables, for backward
1158        compatibility.  Unfortunately it's not feasible to have the env
1159        vars just set defaults, at least in xboard.  Ugh.
1160     */
1161     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1162       ZippyInit();
1163     }
1164 #endif
1165
1166     if (!appData.icsActive) {
1167       char buf[MSG_SIZ];
1168       int len;
1169
1170       /* Check for variants that are supported only in ICS mode,
1171          or not at all.  Some that are accepted here nevertheless
1172          have bugs; see comments below.
1173       */
1174       VariantClass variant = StringToVariant(appData.variant);
1175       switch (variant) {
1176       case VariantBughouse:     /* need four players and two boards */
1177       case VariantKriegspiel:   /* need to hide pieces and move details */
1178         /* case VariantFischeRandom: (Fabien: moved below) */
1179         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1180         if( (len >= MSG_SIZ) && appData.debugMode )
1181           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1182
1183         DisplayFatalError(buf, 0, 2);
1184         return;
1185
1186       case VariantUnknown:
1187       case VariantLoadable:
1188       case Variant29:
1189       case Variant30:
1190       case Variant31:
1191       case Variant32:
1192       case Variant33:
1193       case Variant34:
1194       case Variant35:
1195       case Variant36:
1196       default:
1197         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1198         if( (len >= MSG_SIZ) && appData.debugMode )
1199           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1200
1201         DisplayFatalError(buf, 0, 2);
1202         return;
1203
1204       case VariantNormal:     /* definitely works! */
1205         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1206           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1207           return;
1208         }
1209       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1210       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1211       case VariantGothic:     /* [HGM] should work */
1212       case VariantCapablanca: /* [HGM] should work */
1213       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1214       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1215       case VariantChu:        /* [HGM] experimental */
1216       case VariantKnightmate: /* [HGM] should work */
1217       case VariantCylinder:   /* [HGM] untested */
1218       case VariantFalcon:     /* [HGM] untested */
1219       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1220                                  offboard interposition not understood */
1221       case VariantWildCastle: /* pieces not automatically shuffled */
1222       case VariantNoCastle:   /* pieces not automatically shuffled */
1223       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1224       case VariantLosers:     /* should work except for win condition,
1225                                  and doesn't know captures are mandatory */
1226       case VariantSuicide:    /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantGiveaway:   /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantTwoKings:   /* should work */
1231       case VariantAtomic:     /* should work except for win condition */
1232       case Variant3Check:     /* should work except for win condition */
1233       case VariantShatranj:   /* should work except for all win conditions */
1234       case VariantMakruk:     /* should work except for draw countdown */
1235       case VariantASEAN :     /* should work except for draw countdown */
1236       case VariantBerolina:   /* might work if TestLegality is off */
1237       case VariantCapaRandom: /* should work */
1238       case VariantJanus:      /* should work */
1239       case VariantSuper:      /* experimental */
1240       case VariantGreat:      /* experimental, requires legality testing to be off */
1241       case VariantSChess:     /* S-Chess, should work */
1242       case VariantGrand:      /* should work */
1243       case VariantSpartan:    /* should work */
1244       case VariantLion:       /* should work */
1245       case VariantChuChess:   /* should work */
1246         break;
1247       }
1248     }
1249
1250 }
1251
1252 int
1253 NextIntegerFromString (char ** str, long * value)
1254 {
1255     int result = -1;
1256     char * s = *str;
1257
1258     while( *s == ' ' || *s == '\t' ) {
1259         s++;
1260     }
1261
1262     *value = 0;
1263
1264     if( *s >= '0' && *s <= '9' ) {
1265         while( *s >= '0' && *s <= '9' ) {
1266             *value = *value * 10 + (*s - '0');
1267             s++;
1268         }
1269
1270         result = 0;
1271     }
1272
1273     *str = s;
1274
1275     return result;
1276 }
1277
1278 int
1279 NextTimeControlFromString (char ** str, long * value)
1280 {
1281     long temp;
1282     int result = NextIntegerFromString( str, &temp );
1283
1284     if( result == 0 ) {
1285         *value = temp * 60; /* Minutes */
1286         if( **str == ':' ) {
1287             (*str)++;
1288             result = NextIntegerFromString( str, &temp );
1289             *value += temp; /* Seconds */
1290         }
1291     }
1292
1293     return result;
1294 }
1295
1296 int
1297 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1298 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1299     int result = -1, type = 0; long temp, temp2;
1300
1301     if(**str != ':') return -1; // old params remain in force!
1302     (*str)++;
1303     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1304     if( NextIntegerFromString( str, &temp ) ) return -1;
1305     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1306
1307     if(**str != '/') {
1308         /* time only: incremental or sudden-death time control */
1309         if(**str == '+') { /* increment follows; read it */
1310             (*str)++;
1311             if(**str == '!') type = *(*str)++; // Bronstein TC
1312             if(result = NextIntegerFromString( str, &temp2)) return -1;
1313             *inc = temp2 * 1000;
1314             if(**str == '.') { // read fraction of increment
1315                 char *start = ++(*str);
1316                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1317                 temp2 *= 1000;
1318                 while(start++ < *str) temp2 /= 10;
1319                 *inc += temp2;
1320             }
1321         } else *inc = 0;
1322         *moves = 0; *tc = temp * 1000; *incType = type;
1323         return 0;
1324     }
1325
1326     (*str)++; /* classical time control */
1327     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1328
1329     if(result == 0) {
1330         *moves = temp;
1331         *tc    = temp2 * 1000;
1332         *inc   = 0;
1333         *incType = type;
1334     }
1335     return result;
1336 }
1337
1338 int
1339 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1340 {   /* [HGM] get time to add from the multi-session time-control string */
1341     int incType, moves=1; /* kludge to force reading of first session */
1342     long time, increment;
1343     char *s = tcString;
1344
1345     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1346     do {
1347         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1348         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1349         if(movenr == -1) return time;    /* last move before new session     */
1350         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1351         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1352         if(!moves) return increment;     /* current session is incremental   */
1353         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1354     } while(movenr >= -1);               /* try again for next session       */
1355
1356     return 0; // no new time quota on this move
1357 }
1358
1359 int
1360 ParseTimeControl (char *tc, float ti, int mps)
1361 {
1362   long tc1;
1363   long tc2;
1364   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1365   int min, sec=0;
1366
1367   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1368   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1369       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1370   if(ti > 0) {
1371
1372     if(mps)
1373       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1374     else
1375       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1376   } else {
1377     if(mps)
1378       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1379     else
1380       snprintf(buf, MSG_SIZ, ":%s", mytc);
1381   }
1382   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1383
1384   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1385     return FALSE;
1386   }
1387
1388   if( *tc == '/' ) {
1389     /* Parse second time control */
1390     tc++;
1391
1392     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1393       return FALSE;
1394     }
1395
1396     if( tc2 == 0 ) {
1397       return FALSE;
1398     }
1399
1400     timeControl_2 = tc2 * 1000;
1401   }
1402   else {
1403     timeControl_2 = 0;
1404   }
1405
1406   if( tc1 == 0 ) {
1407     return FALSE;
1408   }
1409
1410   timeControl = tc1 * 1000;
1411
1412   if (ti >= 0) {
1413     timeIncrement = ti * 1000;  /* convert to ms */
1414     movesPerSession = 0;
1415   } else {
1416     timeIncrement = 0;
1417     movesPerSession = mps;
1418   }
1419   return TRUE;
1420 }
1421
1422 void
1423 InitBackEnd2 ()
1424 {
1425     if (appData.debugMode) {
1426 #    ifdef __GIT_VERSION
1427       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1428 #    else
1429       fprintf(debugFP, "Version: %s\n", programVersion);
1430 #    endif
1431     }
1432     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1433
1434     set_cont_sequence(appData.wrapContSeq);
1435     if (appData.matchGames > 0) {
1436         appData.matchMode = TRUE;
1437     } else if (appData.matchMode) {
1438         appData.matchGames = 1;
1439     }
1440     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1441         appData.matchGames = appData.sameColorGames;
1442     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1443         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1444         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1445     }
1446     Reset(TRUE, FALSE);
1447     if (appData.noChessProgram || first.protocolVersion == 1) {
1448       InitBackEnd3();
1449     } else {
1450       /* kludge: allow timeout for initial "feature" commands */
1451       FreezeUI();
1452       DisplayMessage("", _("Starting chess program"));
1453       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1454     }
1455 }
1456
1457 int
1458 CalculateIndex (int index, int gameNr)
1459 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1460     int res;
1461     if(index > 0) return index; // fixed nmber
1462     if(index == 0) return 1;
1463     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1464     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1465     return res;
1466 }
1467
1468 int
1469 LoadGameOrPosition (int gameNr)
1470 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1471     if (*appData.loadGameFile != NULLCHAR) {
1472         if (!LoadGameFromFile(appData.loadGameFile,
1473                 CalculateIndex(appData.loadGameIndex, gameNr),
1474                               appData.loadGameFile, FALSE)) {
1475             DisplayFatalError(_("Bad game file"), 0, 1);
1476             return 0;
1477         }
1478     } else if (*appData.loadPositionFile != NULLCHAR) {
1479         if (!LoadPositionFromFile(appData.loadPositionFile,
1480                 CalculateIndex(appData.loadPositionIndex, gameNr),
1481                                   appData.loadPositionFile)) {
1482             DisplayFatalError(_("Bad position file"), 0, 1);
1483             return 0;
1484         }
1485     }
1486     return 1;
1487 }
1488
1489 void
1490 ReserveGame (int gameNr, char resChar)
1491 {
1492     FILE *tf = fopen(appData.tourneyFile, "r+");
1493     char *p, *q, c, buf[MSG_SIZ];
1494     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1495     safeStrCpy(buf, lastMsg, MSG_SIZ);
1496     DisplayMessage(_("Pick new game"), "");
1497     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1498     ParseArgsFromFile(tf);
1499     p = q = appData.results;
1500     if(appData.debugMode) {
1501       char *r = appData.participants;
1502       fprintf(debugFP, "results = '%s'\n", p);
1503       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1504       fprintf(debugFP, "\n");
1505     }
1506     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1507     nextGame = q - p;
1508     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1509     safeStrCpy(q, p, strlen(p) + 2);
1510     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1511     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1512     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1513         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1514         q[nextGame] = '*';
1515     }
1516     fseek(tf, -(strlen(p)+4), SEEK_END);
1517     c = fgetc(tf);
1518     if(c != '"') // depending on DOS or Unix line endings we can be one off
1519          fseek(tf, -(strlen(p)+2), SEEK_END);
1520     else fseek(tf, -(strlen(p)+3), SEEK_END);
1521     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1522     DisplayMessage(buf, "");
1523     free(p); appData.results = q;
1524     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1525        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1526       int round = appData.defaultMatchGames * appData.tourneyType;
1527       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1528          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1529         UnloadEngine(&first);  // next game belongs to other pairing;
1530         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1531     }
1532     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1533 }
1534
1535 void
1536 MatchEvent (int mode)
1537 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1538         int dummy;
1539         if(matchMode) { // already in match mode: switch it off
1540             abortMatch = TRUE;
1541             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1542             return;
1543         }
1544 //      if(gameMode != BeginningOfGame) {
1545 //          DisplayError(_("You can only start a match from the initial position."), 0);
1546 //          return;
1547 //      }
1548         abortMatch = FALSE;
1549         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1550         /* Set up machine vs. machine match */
1551         nextGame = 0;
1552         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1553         if(appData.tourneyFile[0]) {
1554             ReserveGame(-1, 0);
1555             if(nextGame > appData.matchGames) {
1556                 char buf[MSG_SIZ];
1557                 if(strchr(appData.results, '*') == NULL) {
1558                     FILE *f;
1559                     appData.tourneyCycles++;
1560                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1561                         fclose(f);
1562                         NextTourneyGame(-1, &dummy);
1563                         ReserveGame(-1, 0);
1564                         if(nextGame <= appData.matchGames) {
1565                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1566                             matchMode = mode;
1567                             ScheduleDelayedEvent(NextMatchGame, 10000);
1568                             return;
1569                         }
1570                     }
1571                 }
1572                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1573                 DisplayError(buf, 0);
1574                 appData.tourneyFile[0] = 0;
1575                 return;
1576             }
1577         } else
1578         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1579             DisplayFatalError(_("Can't have a match with no chess programs"),
1580                               0, 2);
1581             return;
1582         }
1583         matchMode = mode;
1584         matchGame = roundNr = 1;
1585         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1586         NextMatchGame();
1587 }
1588
1589 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1590
1591 void
1592 InitBackEnd3 P((void))
1593 {
1594     GameMode initialMode;
1595     char buf[MSG_SIZ];
1596     int err, len;
1597
1598     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1599        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1600         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1601        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1602        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1603         char c, *q = first.variants, *p = strchr(q, ',');
1604         if(p) *p = NULLCHAR;
1605         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1606             int w, h, s;
1607             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1608                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1609             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1610             Reset(TRUE, FALSE);         // and re-initialize
1611         }
1612         if(p) *p = ',';
1613     }
1614
1615     InitChessProgram(&first, startedFromSetupPosition);
1616
1617     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1618         free(programVersion);
1619         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1620         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1621         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1622     }
1623
1624     if (appData.icsActive) {
1625 #ifdef WIN32
1626         /* [DM] Make a console window if needed [HGM] merged ifs */
1627         ConsoleCreate();
1628 #endif
1629         err = establish();
1630         if (err != 0)
1631           {
1632             if (*appData.icsCommPort != NULLCHAR)
1633               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1634                              appData.icsCommPort);
1635             else
1636               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1637                         appData.icsHost, appData.icsPort);
1638
1639             if( (len >= MSG_SIZ) && appData.debugMode )
1640               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1641
1642             DisplayFatalError(buf, err, 1);
1643             return;
1644         }
1645         SetICSMode();
1646         telnetISR =
1647           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1648         fromUserISR =
1649           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1650         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1651             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1652     } else if (appData.noChessProgram) {
1653         SetNCPMode();
1654     } else {
1655         SetGNUMode();
1656     }
1657
1658     if (*appData.cmailGameName != NULLCHAR) {
1659         SetCmailMode();
1660         OpenLoopback(&cmailPR);
1661         cmailISR =
1662           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1663     }
1664
1665     ThawUI();
1666     DisplayMessage("", "");
1667     if (StrCaseCmp(appData.initialMode, "") == 0) {
1668       initialMode = BeginningOfGame;
1669       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1670         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1671         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1672         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1673         ModeHighlight();
1674       }
1675     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1676       initialMode = TwoMachinesPlay;
1677     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1678       initialMode = AnalyzeFile;
1679     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1680       initialMode = AnalyzeMode;
1681     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1682       initialMode = MachinePlaysWhite;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1684       initialMode = MachinePlaysBlack;
1685     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1686       initialMode = EditGame;
1687     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1688       initialMode = EditPosition;
1689     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1690       initialMode = Training;
1691     } else {
1692       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1693       if( (len >= MSG_SIZ) && appData.debugMode )
1694         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1695
1696       DisplayFatalError(buf, 0, 2);
1697       return;
1698     }
1699
1700     if (appData.matchMode) {
1701         if(appData.tourneyFile[0]) { // start tourney from command line
1702             FILE *f;
1703             if(f = fopen(appData.tourneyFile, "r")) {
1704                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1705                 fclose(f);
1706                 appData.clockMode = TRUE;
1707                 SetGNUMode();
1708             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1709         }
1710         MatchEvent(TRUE);
1711     } else if (*appData.cmailGameName != NULLCHAR) {
1712         /* Set up cmail mode */
1713         ReloadCmailMsgEvent(TRUE);
1714     } else {
1715         /* Set up other modes */
1716         if (initialMode == AnalyzeFile) {
1717           if (*appData.loadGameFile == NULLCHAR) {
1718             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1719             return;
1720           }
1721         }
1722         if (*appData.loadGameFile != NULLCHAR) {
1723             (void) LoadGameFromFile(appData.loadGameFile,
1724                                     appData.loadGameIndex,
1725                                     appData.loadGameFile, TRUE);
1726         } else if (*appData.loadPositionFile != NULLCHAR) {
1727             (void) LoadPositionFromFile(appData.loadPositionFile,
1728                                         appData.loadPositionIndex,
1729                                         appData.loadPositionFile);
1730             /* [HGM] try to make self-starting even after FEN load */
1731             /* to allow automatic setup of fairy variants with wtm */
1732             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1733                 gameMode = BeginningOfGame;
1734                 setboardSpoiledMachineBlack = 1;
1735             }
1736             /* [HGM] loadPos: make that every new game uses the setup */
1737             /* from file as long as we do not switch variant          */
1738             if(!blackPlaysFirst) {
1739                 startedFromPositionFile = TRUE;
1740                 CopyBoard(filePosition, boards[0]);
1741             }
1742         }
1743         if (initialMode == AnalyzeMode) {
1744           if (appData.noChessProgram) {
1745             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1746             return;
1747           }
1748           if (appData.icsActive) {
1749             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1750             return;
1751           }
1752           AnalyzeModeEvent();
1753         } else if (initialMode == AnalyzeFile) {
1754           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1755           ShowThinkingEvent();
1756           AnalyzeFileEvent();
1757           AnalysisPeriodicEvent(1);
1758         } else if (initialMode == MachinePlaysWhite) {
1759           if (appData.noChessProgram) {
1760             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1761                               0, 2);
1762             return;
1763           }
1764           if (appData.icsActive) {
1765             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1766                               0, 2);
1767             return;
1768           }
1769           MachineWhiteEvent();
1770         } else if (initialMode == MachinePlaysBlack) {
1771           if (appData.noChessProgram) {
1772             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1773                               0, 2);
1774             return;
1775           }
1776           if (appData.icsActive) {
1777             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1778                               0, 2);
1779             return;
1780           }
1781           MachineBlackEvent();
1782         } else if (initialMode == TwoMachinesPlay) {
1783           if (appData.noChessProgram) {
1784             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1785                               0, 2);
1786             return;
1787           }
1788           if (appData.icsActive) {
1789             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1790                               0, 2);
1791             return;
1792           }
1793           TwoMachinesEvent();
1794         } else if (initialMode == EditGame) {
1795           EditGameEvent();
1796         } else if (initialMode == EditPosition) {
1797           EditPositionEvent();
1798         } else if (initialMode == Training) {
1799           if (*appData.loadGameFile == NULLCHAR) {
1800             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1801             return;
1802           }
1803           TrainingEvent();
1804         }
1805     }
1806 }
1807
1808 void
1809 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1810 {
1811     DisplayBook(current+1);
1812
1813     MoveHistorySet( movelist, first, last, current, pvInfoList );
1814
1815     EvalGraphSet( first, last, current, pvInfoList );
1816
1817     MakeEngineOutputTitle();
1818 }
1819
1820 /*
1821  * Establish will establish a contact to a remote host.port.
1822  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1823  *  used to talk to the host.
1824  * Returns 0 if okay, error code if not.
1825  */
1826 int
1827 establish ()
1828 {
1829     char buf[MSG_SIZ];
1830
1831     if (*appData.icsCommPort != NULLCHAR) {
1832         /* Talk to the host through a serial comm port */
1833         return OpenCommPort(appData.icsCommPort, &icsPR);
1834
1835     } else if (*appData.gateway != NULLCHAR) {
1836         if (*appData.remoteShell == NULLCHAR) {
1837             /* Use the rcmd protocol to run telnet program on a gateway host */
1838             snprintf(buf, sizeof(buf), "%s %s %s",
1839                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1840             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1841
1842         } else {
1843             /* Use the rsh program to run telnet program on a gateway host */
1844             if (*appData.remoteUser == NULLCHAR) {
1845                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1846                         appData.gateway, appData.telnetProgram,
1847                         appData.icsHost, appData.icsPort);
1848             } else {
1849                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1850                         appData.remoteShell, appData.gateway,
1851                         appData.remoteUser, appData.telnetProgram,
1852                         appData.icsHost, appData.icsPort);
1853             }
1854             return StartChildProcess(buf, "", &icsPR);
1855
1856         }
1857     } else if (appData.useTelnet) {
1858         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1859
1860     } else {
1861         /* TCP socket interface differs somewhat between
1862            Unix and NT; handle details in the front end.
1863            */
1864         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1865     }
1866 }
1867
1868 void
1869 EscapeExpand (char *p, char *q)
1870 {       // [HGM] initstring: routine to shape up string arguments
1871         while(*p++ = *q++) if(p[-1] == '\\')
1872             switch(*q++) {
1873                 case 'n': p[-1] = '\n'; break;
1874                 case 'r': p[-1] = '\r'; break;
1875                 case 't': p[-1] = '\t'; break;
1876                 case '\\': p[-1] = '\\'; break;
1877                 case 0: *p = 0; return;
1878                 default: p[-1] = q[-1]; break;
1879             }
1880 }
1881
1882 void
1883 show_bytes (FILE *fp, char *buf, int count)
1884 {
1885     while (count--) {
1886         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1887             fprintf(fp, "\\%03o", *buf & 0xff);
1888         } else {
1889             putc(*buf, fp);
1890         }
1891         buf++;
1892     }
1893     fflush(fp);
1894 }
1895
1896 /* Returns an errno value */
1897 int
1898 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1899 {
1900     char buf[8192], *p, *q, *buflim;
1901     int left, newcount, outcount;
1902
1903     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1904         *appData.gateway != NULLCHAR) {
1905         if (appData.debugMode) {
1906             fprintf(debugFP, ">ICS: ");
1907             show_bytes(debugFP, message, count);
1908             fprintf(debugFP, "\n");
1909         }
1910         return OutputToProcess(pr, message, count, outError);
1911     }
1912
1913     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1914     p = message;
1915     q = buf;
1916     left = count;
1917     newcount = 0;
1918     while (left) {
1919         if (q >= buflim) {
1920             if (appData.debugMode) {
1921                 fprintf(debugFP, ">ICS: ");
1922                 show_bytes(debugFP, buf, newcount);
1923                 fprintf(debugFP, "\n");
1924             }
1925             outcount = OutputToProcess(pr, buf, newcount, outError);
1926             if (outcount < newcount) return -1; /* to be sure */
1927             q = buf;
1928             newcount = 0;
1929         }
1930         if (*p == '\n') {
1931             *q++ = '\r';
1932             newcount++;
1933         } else if (((unsigned char) *p) == TN_IAC) {
1934             *q++ = (char) TN_IAC;
1935             newcount ++;
1936         }
1937         *q++ = *p++;
1938         newcount++;
1939         left--;
1940     }
1941     if (appData.debugMode) {
1942         fprintf(debugFP, ">ICS: ");
1943         show_bytes(debugFP, buf, newcount);
1944         fprintf(debugFP, "\n");
1945     }
1946     outcount = OutputToProcess(pr, buf, newcount, outError);
1947     if (outcount < newcount) return -1; /* to be sure */
1948     return count;
1949 }
1950
1951 void
1952 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1953 {
1954     int outError, outCount;
1955     static int gotEof = 0;
1956     static FILE *ini;
1957
1958     /* Pass data read from player on to ICS */
1959     if (count > 0) {
1960         gotEof = 0;
1961         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1962         if (outCount < count) {
1963             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1964         }
1965         if(have_sent_ICS_logon == 2) {
1966           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1967             fprintf(ini, "%s", message);
1968             have_sent_ICS_logon = 3;
1969           } else
1970             have_sent_ICS_logon = 1;
1971         } else if(have_sent_ICS_logon == 3) {
1972             fprintf(ini, "%s", message);
1973             fclose(ini);
1974           have_sent_ICS_logon = 1;
1975         }
1976     } else if (count < 0) {
1977         RemoveInputSource(isr);
1978         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1979     } else if (gotEof++ > 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1982     }
1983 }
1984
1985 void
1986 KeepAlive ()
1987 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1988     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1989     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1990     SendToICS("date\n");
1991     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1992 }
1993
1994 /* added routine for printf style output to ics */
1995 void
1996 ics_printf (char *format, ...)
1997 {
1998     char buffer[MSG_SIZ];
1999     va_list args;
2000
2001     va_start(args, format);
2002     vsnprintf(buffer, sizeof(buffer), format, args);
2003     buffer[sizeof(buffer)-1] = '\0';
2004     SendToICS(buffer);
2005     va_end(args);
2006 }
2007
2008 void
2009 SendToICS (char *s)
2010 {
2011     int count, outCount, outError;
2012
2013     if (icsPR == NoProc) return;
2014
2015     count = strlen(s);
2016     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2017     if (outCount < count) {
2018         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2019     }
2020 }
2021
2022 /* This is used for sending logon scripts to the ICS. Sending
2023    without a delay causes problems when using timestamp on ICC
2024    (at least on my machine). */
2025 void
2026 SendToICSDelayed (char *s, long msdelay)
2027 {
2028     int count, outCount, outError;
2029
2030     if (icsPR == NoProc) return;
2031
2032     count = strlen(s);
2033     if (appData.debugMode) {
2034         fprintf(debugFP, ">ICS: ");
2035         show_bytes(debugFP, s, count);
2036         fprintf(debugFP, "\n");
2037     }
2038     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2039                                       msdelay);
2040     if (outCount < count) {
2041         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2042     }
2043 }
2044
2045
2046 /* Remove all highlighting escape sequences in s
2047    Also deletes any suffix starting with '('
2048    */
2049 char *
2050 StripHighlightAndTitle (char *s)
2051 {
2052     static char retbuf[MSG_SIZ];
2053     char *p = retbuf;
2054
2055     while (*s != NULLCHAR) {
2056         while (*s == '\033') {
2057             while (*s != NULLCHAR && !isalpha(*s)) s++;
2058             if (*s != NULLCHAR) s++;
2059         }
2060         while (*s != NULLCHAR && *s != '\033') {
2061             if (*s == '(' || *s == '[') {
2062                 *p = NULLCHAR;
2063                 return retbuf;
2064             }
2065             *p++ = *s++;
2066         }
2067     }
2068     *p = NULLCHAR;
2069     return retbuf;
2070 }
2071
2072 /* Remove all highlighting escape sequences in s */
2073 char *
2074 StripHighlight (char *s)
2075 {
2076     static char retbuf[MSG_SIZ];
2077     char *p = retbuf;
2078
2079     while (*s != NULLCHAR) {
2080         while (*s == '\033') {
2081             while (*s != NULLCHAR && !isalpha(*s)) s++;
2082             if (*s != NULLCHAR) s++;
2083         }
2084         while (*s != NULLCHAR && *s != '\033') {
2085             *p++ = *s++;
2086         }
2087     }
2088     *p = NULLCHAR;
2089     return retbuf;
2090 }
2091
2092 char engineVariant[MSG_SIZ];
2093 char *variantNames[] = VARIANT_NAMES;
2094 char *
2095 VariantName (VariantClass v)
2096 {
2097     if(v == VariantUnknown || *engineVariant) return engineVariant;
2098     return variantNames[v];
2099 }
2100
2101
2102 /* Identify a variant from the strings the chess servers use or the
2103    PGN Variant tag names we use. */
2104 VariantClass
2105 StringToVariant (char *e)
2106 {
2107     char *p;
2108     int wnum = -1;
2109     VariantClass v = VariantNormal;
2110     int i, found = FALSE;
2111     char buf[MSG_SIZ], c;
2112     int len;
2113
2114     if (!e) return v;
2115
2116     /* [HGM] skip over optional board-size prefixes */
2117     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2118         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2119         while( *e++ != '_');
2120     }
2121
2122     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2123         v = VariantNormal;
2124         found = TRUE;
2125     } else
2126     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2127       if (p = StrCaseStr(e, variantNames[i])) {
2128         if(p && i >= VariantShogi && (p != e || isalpha(p[strlen(variantNames[i])]))) continue;
2129         v = (VariantClass) i;
2130         found = TRUE;
2131         break;
2132       }
2133     }
2134
2135     if (!found) {
2136       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2137           || StrCaseStr(e, "wild/fr")
2138           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2139         v = VariantFischeRandom;
2140       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2141                  (i = 1, p = StrCaseStr(e, "w"))) {
2142         p += i;
2143         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2144         if (isdigit(*p)) {
2145           wnum = atoi(p);
2146         } else {
2147           wnum = -1;
2148         }
2149         switch (wnum) {
2150         case 0: /* FICS only, actually */
2151         case 1:
2152           /* Castling legal even if K starts on d-file */
2153           v = VariantWildCastle;
2154           break;
2155         case 2:
2156         case 3:
2157         case 4:
2158           /* Castling illegal even if K & R happen to start in
2159              normal positions. */
2160           v = VariantNoCastle;
2161           break;
2162         case 5:
2163         case 7:
2164         case 8:
2165         case 10:
2166         case 11:
2167         case 12:
2168         case 13:
2169         case 14:
2170         case 15:
2171         case 18:
2172         case 19:
2173           /* Castling legal iff K & R start in normal positions */
2174           v = VariantNormal;
2175           break;
2176         case 6:
2177         case 20:
2178         case 21:
2179           /* Special wilds for position setup; unclear what to do here */
2180           v = VariantLoadable;
2181           break;
2182         case 9:
2183           /* Bizarre ICC game */
2184           v = VariantTwoKings;
2185           break;
2186         case 16:
2187           v = VariantKriegspiel;
2188           break;
2189         case 17:
2190           v = VariantLosers;
2191           break;
2192         case 22:
2193           v = VariantFischeRandom;
2194           break;
2195         case 23:
2196           v = VariantCrazyhouse;
2197           break;
2198         case 24:
2199           v = VariantBughouse;
2200           break;
2201         case 25:
2202           v = Variant3Check;
2203           break;
2204         case 26:
2205           /* Not quite the same as FICS suicide! */
2206           v = VariantGiveaway;
2207           break;
2208         case 27:
2209           v = VariantAtomic;
2210           break;
2211         case 28:
2212           v = VariantShatranj;
2213           break;
2214
2215         /* Temporary names for future ICC types.  The name *will* change in
2216            the next xboard/WinBoard release after ICC defines it. */
2217         case 29:
2218           v = Variant29;
2219           break;
2220         case 30:
2221           v = Variant30;
2222           break;
2223         case 31:
2224           v = Variant31;
2225           break;
2226         case 32:
2227           v = Variant32;
2228           break;
2229         case 33:
2230           v = Variant33;
2231           break;
2232         case 34:
2233           v = Variant34;
2234           break;
2235         case 35:
2236           v = Variant35;
2237           break;
2238         case 36:
2239           v = Variant36;
2240           break;
2241         case 37:
2242           v = VariantShogi;
2243           break;
2244         case 38:
2245           v = VariantXiangqi;
2246           break;
2247         case 39:
2248           v = VariantCourier;
2249           break;
2250         case 40:
2251           v = VariantGothic;
2252           break;
2253         case 41:
2254           v = VariantCapablanca;
2255           break;
2256         case 42:
2257           v = VariantKnightmate;
2258           break;
2259         case 43:
2260           v = VariantFairy;
2261           break;
2262         case 44:
2263           v = VariantCylinder;
2264           break;
2265         case 45:
2266           v = VariantFalcon;
2267           break;
2268         case 46:
2269           v = VariantCapaRandom;
2270           break;
2271         case 47:
2272           v = VariantBerolina;
2273           break;
2274         case 48:
2275           v = VariantJanus;
2276           break;
2277         case 49:
2278           v = VariantSuper;
2279           break;
2280         case 50:
2281           v = VariantGreat;
2282           break;
2283         case -1:
2284           /* Found "wild" or "w" in the string but no number;
2285              must assume it's normal chess. */
2286           v = VariantNormal;
2287           break;
2288         default:
2289           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2290           if( (len >= MSG_SIZ) && appData.debugMode )
2291             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2292
2293           DisplayError(buf, 0);
2294           v = VariantUnknown;
2295           break;
2296         }
2297       }
2298     }
2299     if (appData.debugMode) {
2300       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2301               e, wnum, VariantName(v));
2302     }
2303     return v;
2304 }
2305
2306 static int leftover_start = 0, leftover_len = 0;
2307 char star_match[STAR_MATCH_N][MSG_SIZ];
2308
2309 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2310    advance *index beyond it, and set leftover_start to the new value of
2311    *index; else return FALSE.  If pattern contains the character '*', it
2312    matches any sequence of characters not containing '\r', '\n', or the
2313    character following the '*' (if any), and the matched sequence(s) are
2314    copied into star_match.
2315    */
2316 int
2317 looking_at ( char *buf, int *index, char *pattern)
2318 {
2319     char *bufp = &buf[*index], *patternp = pattern;
2320     int star_count = 0;
2321     char *matchp = star_match[0];
2322
2323     for (;;) {
2324         if (*patternp == NULLCHAR) {
2325             *index = leftover_start = bufp - buf;
2326             *matchp = NULLCHAR;
2327             return TRUE;
2328         }
2329         if (*bufp == NULLCHAR) return FALSE;
2330         if (*patternp == '*') {
2331             if (*bufp == *(patternp + 1)) {
2332                 *matchp = NULLCHAR;
2333                 matchp = star_match[++star_count];
2334                 patternp += 2;
2335                 bufp++;
2336                 continue;
2337             } else if (*bufp == '\n' || *bufp == '\r') {
2338                 patternp++;
2339                 if (*patternp == NULLCHAR)
2340                   continue;
2341                 else
2342                   return FALSE;
2343             } else {
2344                 *matchp++ = *bufp++;
2345                 continue;
2346             }
2347         }
2348         if (*patternp != *bufp) return FALSE;
2349         patternp++;
2350         bufp++;
2351     }
2352 }
2353
2354 void
2355 SendToPlayer (char *data, int length)
2356 {
2357     int error, outCount;
2358     outCount = OutputToProcess(NoProc, data, length, &error);
2359     if (outCount < length) {
2360         DisplayFatalError(_("Error writing to display"), error, 1);
2361     }
2362 }
2363
2364 void
2365 PackHolding (char packed[], char *holding)
2366 {
2367     char *p = holding;
2368     char *q = packed;
2369     int runlength = 0;
2370     int curr = 9999;
2371     do {
2372         if (*p == curr) {
2373             runlength++;
2374         } else {
2375             switch (runlength) {
2376               case 0:
2377                 break;
2378               case 1:
2379                 *q++ = curr;
2380                 break;
2381               case 2:
2382                 *q++ = curr;
2383                 *q++ = curr;
2384                 break;
2385               default:
2386                 sprintf(q, "%d", runlength);
2387                 while (*q) q++;
2388                 *q++ = curr;
2389                 break;
2390             }
2391             runlength = 1;
2392             curr = *p;
2393         }
2394     } while (*p++);
2395     *q = NULLCHAR;
2396 }
2397
2398 /* Telnet protocol requests from the front end */
2399 void
2400 TelnetRequest (unsigned char ddww, unsigned char option)
2401 {
2402     unsigned char msg[3];
2403     int outCount, outError;
2404
2405     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2406
2407     if (appData.debugMode) {
2408         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2409         switch (ddww) {
2410           case TN_DO:
2411             ddwwStr = "DO";
2412             break;
2413           case TN_DONT:
2414             ddwwStr = "DONT";
2415             break;
2416           case TN_WILL:
2417             ddwwStr = "WILL";
2418             break;
2419           case TN_WONT:
2420             ddwwStr = "WONT";
2421             break;
2422           default:
2423             ddwwStr = buf1;
2424             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2425             break;
2426         }
2427         switch (option) {
2428           case TN_ECHO:
2429             optionStr = "ECHO";
2430             break;
2431           default:
2432             optionStr = buf2;
2433             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2434             break;
2435         }
2436         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2437     }
2438     msg[0] = TN_IAC;
2439     msg[1] = ddww;
2440     msg[2] = option;
2441     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2442     if (outCount < 3) {
2443         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2444     }
2445 }
2446
2447 void
2448 DoEcho ()
2449 {
2450     if (!appData.icsActive) return;
2451     TelnetRequest(TN_DO, TN_ECHO);
2452 }
2453
2454 void
2455 DontEcho ()
2456 {
2457     if (!appData.icsActive) return;
2458     TelnetRequest(TN_DONT, TN_ECHO);
2459 }
2460
2461 void
2462 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2463 {
2464     /* put the holdings sent to us by the server on the board holdings area */
2465     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2466     char p;
2467     ChessSquare piece;
2468
2469     if(gameInfo.holdingsWidth < 2)  return;
2470     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2471         return; // prevent overwriting by pre-board holdings
2472
2473     if( (int)lowestPiece >= BlackPawn ) {
2474         holdingsColumn = 0;
2475         countsColumn = 1;
2476         holdingsStartRow = BOARD_HEIGHT-1;
2477         direction = -1;
2478     } else {
2479         holdingsColumn = BOARD_WIDTH-1;
2480         countsColumn = BOARD_WIDTH-2;
2481         holdingsStartRow = 0;
2482         direction = 1;
2483     }
2484
2485     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2486         board[i][holdingsColumn] = EmptySquare;
2487         board[i][countsColumn]   = (ChessSquare) 0;
2488     }
2489     while( (p=*holdings++) != NULLCHAR ) {
2490         piece = CharToPiece( ToUpper(p) );
2491         if(piece == EmptySquare) continue;
2492         /*j = (int) piece - (int) WhitePawn;*/
2493         j = PieceToNumber(piece);
2494         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2495         if(j < 0) continue;               /* should not happen */
2496         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2497         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2498         board[holdingsStartRow+j*direction][countsColumn]++;
2499     }
2500 }
2501
2502
2503 void
2504 VariantSwitch (Board board, VariantClass newVariant)
2505 {
2506    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2507    static Board oldBoard;
2508
2509    startedFromPositionFile = FALSE;
2510    if(gameInfo.variant == newVariant) return;
2511
2512    /* [HGM] This routine is called each time an assignment is made to
2513     * gameInfo.variant during a game, to make sure the board sizes
2514     * are set to match the new variant. If that means adding or deleting
2515     * holdings, we shift the playing board accordingly
2516     * This kludge is needed because in ICS observe mode, we get boards
2517     * of an ongoing game without knowing the variant, and learn about the
2518     * latter only later. This can be because of the move list we requested,
2519     * in which case the game history is refilled from the beginning anyway,
2520     * but also when receiving holdings of a crazyhouse game. In the latter
2521     * case we want to add those holdings to the already received position.
2522     */
2523
2524
2525    if (appData.debugMode) {
2526      fprintf(debugFP, "Switch board from %s to %s\n",
2527              VariantName(gameInfo.variant), VariantName(newVariant));
2528      setbuf(debugFP, NULL);
2529    }
2530    shuffleOpenings = 0;       /* [HGM] shuffle */
2531    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2532    switch(newVariant)
2533      {
2534      case VariantShogi:
2535        newWidth = 9;  newHeight = 9;
2536        gameInfo.holdingsSize = 7;
2537      case VariantBughouse:
2538      case VariantCrazyhouse:
2539        newHoldingsWidth = 2; break;
2540      case VariantGreat:
2541        newWidth = 10;
2542      case VariantSuper:
2543        newHoldingsWidth = 2;
2544        gameInfo.holdingsSize = 8;
2545        break;
2546      case VariantGothic:
2547      case VariantCapablanca:
2548      case VariantCapaRandom:
2549        newWidth = 10;
2550      default:
2551        newHoldingsWidth = gameInfo.holdingsSize = 0;
2552      };
2553
2554    if(newWidth  != gameInfo.boardWidth  ||
2555       newHeight != gameInfo.boardHeight ||
2556       newHoldingsWidth != gameInfo.holdingsWidth ) {
2557
2558      /* shift position to new playing area, if needed */
2559      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2560        for(i=0; i<BOARD_HEIGHT; i++)
2561          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2562            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2563              board[i][j];
2564        for(i=0; i<newHeight; i++) {
2565          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2566          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2567        }
2568      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2569        for(i=0; i<BOARD_HEIGHT; i++)
2570          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2571            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2572              board[i][j];
2573      }
2574      board[HOLDINGS_SET] = 0;
2575      gameInfo.boardWidth  = newWidth;
2576      gameInfo.boardHeight = newHeight;
2577      gameInfo.holdingsWidth = newHoldingsWidth;
2578      gameInfo.variant = newVariant;
2579      InitDrawingSizes(-2, 0);
2580    } else gameInfo.variant = newVariant;
2581    CopyBoard(oldBoard, board);   // remember correctly formatted board
2582      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2583    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2584 }
2585
2586 static int loggedOn = FALSE;
2587
2588 /*-- Game start info cache: --*/
2589 int gs_gamenum;
2590 char gs_kind[MSG_SIZ];
2591 static char player1Name[128] = "";
2592 static char player2Name[128] = "";
2593 static char cont_seq[] = "\n\\   ";
2594 static int player1Rating = -1;
2595 static int player2Rating = -1;
2596 /*----------------------------*/
2597
2598 ColorClass curColor = ColorNormal;
2599 int suppressKibitz = 0;
2600
2601 // [HGM] seekgraph
2602 Boolean soughtPending = FALSE;
2603 Boolean seekGraphUp;
2604 #define MAX_SEEK_ADS 200
2605 #define SQUARE 0x80
2606 char *seekAdList[MAX_SEEK_ADS];
2607 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2608 float tcList[MAX_SEEK_ADS];
2609 char colorList[MAX_SEEK_ADS];
2610 int nrOfSeekAds = 0;
2611 int minRating = 1010, maxRating = 2800;
2612 int hMargin = 10, vMargin = 20, h, w;
2613 extern int squareSize, lineGap;
2614
2615 void
2616 PlotSeekAd (int i)
2617 {
2618         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2619         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2620         if(r < minRating+100 && r >=0 ) r = minRating+100;
2621         if(r > maxRating) r = maxRating;
2622         if(tc < 1.f) tc = 1.f;
2623         if(tc > 95.f) tc = 95.f;
2624         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2625         y = ((double)r - minRating)/(maxRating - minRating)
2626             * (h-vMargin-squareSize/8-1) + vMargin;
2627         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2628         if(strstr(seekAdList[i], " u ")) color = 1;
2629         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2630            !strstr(seekAdList[i], "bullet") &&
2631            !strstr(seekAdList[i], "blitz") &&
2632            !strstr(seekAdList[i], "standard") ) color = 2;
2633         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2634         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2635 }
2636
2637 void
2638 PlotSingleSeekAd (int i)
2639 {
2640         PlotSeekAd(i);
2641 }
2642
2643 void
2644 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2645 {
2646         char buf[MSG_SIZ], *ext = "";
2647         VariantClass v = StringToVariant(type);
2648         if(strstr(type, "wild")) {
2649             ext = type + 4; // append wild number
2650             if(v == VariantFischeRandom) type = "chess960"; else
2651             if(v == VariantLoadable) type = "setup"; else
2652             type = VariantName(v);
2653         }
2654         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2655         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2656             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2657             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2658             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2659             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2660             seekNrList[nrOfSeekAds] = nr;
2661             zList[nrOfSeekAds] = 0;
2662             seekAdList[nrOfSeekAds++] = StrSave(buf);
2663             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2664         }
2665 }
2666
2667 void
2668 EraseSeekDot (int i)
2669 {
2670     int x = xList[i], y = yList[i], d=squareSize/4, k;
2671     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2672     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2673     // now replot every dot that overlapped
2674     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2675         int xx = xList[k], yy = yList[k];
2676         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2677             DrawSeekDot(xx, yy, colorList[k]);
2678     }
2679 }
2680
2681 void
2682 RemoveSeekAd (int nr)
2683 {
2684         int i;
2685         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2686             EraseSeekDot(i);
2687             if(seekAdList[i]) free(seekAdList[i]);
2688             seekAdList[i] = seekAdList[--nrOfSeekAds];
2689             seekNrList[i] = seekNrList[nrOfSeekAds];
2690             ratingList[i] = ratingList[nrOfSeekAds];
2691             colorList[i]  = colorList[nrOfSeekAds];
2692             tcList[i] = tcList[nrOfSeekAds];
2693             xList[i]  = xList[nrOfSeekAds];
2694             yList[i]  = yList[nrOfSeekAds];
2695             zList[i]  = zList[nrOfSeekAds];
2696             seekAdList[nrOfSeekAds] = NULL;
2697             break;
2698         }
2699 }
2700
2701 Boolean
2702 MatchSoughtLine (char *line)
2703 {
2704     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2705     int nr, base, inc, u=0; char dummy;
2706
2707     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2708        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2709        (u=1) &&
2710        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2712         // match: compact and save the line
2713         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2714         return TRUE;
2715     }
2716     return FALSE;
2717 }
2718
2719 int
2720 DrawSeekGraph ()
2721 {
2722     int i;
2723     if(!seekGraphUp) return FALSE;
2724     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2725     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2726
2727     DrawSeekBackground(0, 0, w, h);
2728     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2729     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2730     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2731         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2732         yy = h-1-yy;
2733         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2734         if(i%500 == 0) {
2735             char buf[MSG_SIZ];
2736             snprintf(buf, MSG_SIZ, "%d", i);
2737             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2738         }
2739     }
2740     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2741     for(i=1; i<100; i+=(i<10?1:5)) {
2742         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2743         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2744         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2745             char buf[MSG_SIZ];
2746             snprintf(buf, MSG_SIZ, "%d", i);
2747             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2748         }
2749     }
2750     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2751     return TRUE;
2752 }
2753
2754 int
2755 SeekGraphClick (ClickType click, int x, int y, int moving)
2756 {
2757     static int lastDown = 0, displayed = 0, lastSecond;
2758     if(y < 0) return FALSE;
2759     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2760         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2761         if(!seekGraphUp) return FALSE;
2762         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2763         DrawPosition(TRUE, NULL);
2764         return TRUE;
2765     }
2766     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2767         if(click == Release || moving) return FALSE;
2768         nrOfSeekAds = 0;
2769         soughtPending = TRUE;
2770         SendToICS(ics_prefix);
2771         SendToICS("sought\n"); // should this be "sought all"?
2772     } else { // issue challenge based on clicked ad
2773         int dist = 10000; int i, closest = 0, second = 0;
2774         for(i=0; i<nrOfSeekAds; i++) {
2775             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2776             if(d < dist) { dist = d; closest = i; }
2777             second += (d - zList[i] < 120); // count in-range ads
2778             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2779         }
2780         if(dist < 120) {
2781             char buf[MSG_SIZ];
2782             second = (second > 1);
2783             if(displayed != closest || second != lastSecond) {
2784                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2785                 lastSecond = second; displayed = closest;
2786             }
2787             if(click == Press) {
2788                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2789                 lastDown = closest;
2790                 return TRUE;
2791             } // on press 'hit', only show info
2792             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2793             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2794             SendToICS(ics_prefix);
2795             SendToICS(buf);
2796             return TRUE; // let incoming board of started game pop down the graph
2797         } else if(click == Release) { // release 'miss' is ignored
2798             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2799             if(moving == 2) { // right up-click
2800                 nrOfSeekAds = 0; // refresh graph
2801                 soughtPending = TRUE;
2802                 SendToICS(ics_prefix);
2803                 SendToICS("sought\n"); // should this be "sought all"?
2804             }
2805             return TRUE;
2806         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2807         // press miss or release hit 'pop down' seek graph
2808         seekGraphUp = FALSE;
2809         DrawPosition(TRUE, NULL);
2810     }
2811     return TRUE;
2812 }
2813
2814 void
2815 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2816 {
2817 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2818 #define STARTED_NONE 0
2819 #define STARTED_MOVES 1
2820 #define STARTED_BOARD 2
2821 #define STARTED_OBSERVE 3
2822 #define STARTED_HOLDINGS 4
2823 #define STARTED_CHATTER 5
2824 #define STARTED_COMMENT 6
2825 #define STARTED_MOVES_NOHIDE 7
2826
2827     static int started = STARTED_NONE;
2828     static char parse[20000];
2829     static int parse_pos = 0;
2830     static char buf[BUF_SIZE + 1];
2831     static int firstTime = TRUE, intfSet = FALSE;
2832     static ColorClass prevColor = ColorNormal;
2833     static int savingComment = FALSE;
2834     static int cmatch = 0; // continuation sequence match
2835     char *bp;
2836     char str[MSG_SIZ];
2837     int i, oldi;
2838     int buf_len;
2839     int next_out;
2840     int tkind;
2841     int backup;    /* [DM] For zippy color lines */
2842     char *p;
2843     char talker[MSG_SIZ]; // [HGM] chat
2844     int channel, collective=0;
2845
2846     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2847
2848     if (appData.debugMode) {
2849       if (!error) {
2850         fprintf(debugFP, "<ICS: ");
2851         show_bytes(debugFP, data, count);
2852         fprintf(debugFP, "\n");
2853       }
2854     }
2855
2856     if (appData.debugMode) { int f = forwardMostMove;
2857         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2858                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2859                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2860     }
2861     if (count > 0) {
2862         /* If last read ended with a partial line that we couldn't parse,
2863            prepend it to the new read and try again. */
2864         if (leftover_len > 0) {
2865             for (i=0; i<leftover_len; i++)
2866               buf[i] = buf[leftover_start + i];
2867         }
2868
2869     /* copy new characters into the buffer */
2870     bp = buf + leftover_len;
2871     buf_len=leftover_len;
2872     for (i=0; i<count; i++)
2873     {
2874         // ignore these
2875         if (data[i] == '\r')
2876             continue;
2877
2878         // join lines split by ICS?
2879         if (!appData.noJoin)
2880         {
2881             /*
2882                 Joining just consists of finding matches against the
2883                 continuation sequence, and discarding that sequence
2884                 if found instead of copying it.  So, until a match
2885                 fails, there's nothing to do since it might be the
2886                 complete sequence, and thus, something we don't want
2887                 copied.
2888             */
2889             if (data[i] == cont_seq[cmatch])
2890             {
2891                 cmatch++;
2892                 if (cmatch == strlen(cont_seq))
2893                 {
2894                     cmatch = 0; // complete match.  just reset the counter
2895
2896                     /*
2897                         it's possible for the ICS to not include the space
2898                         at the end of the last word, making our [correct]
2899                         join operation fuse two separate words.  the server
2900                         does this when the space occurs at the width setting.
2901                     */
2902                     if (!buf_len || buf[buf_len-1] != ' ')
2903                     {
2904                         *bp++ = ' ';
2905                         buf_len++;
2906                     }
2907                 }
2908                 continue;
2909             }
2910             else if (cmatch)
2911             {
2912                 /*
2913                     match failed, so we have to copy what matched before
2914                     falling through and copying this character.  In reality,
2915                     this will only ever be just the newline character, but
2916                     it doesn't hurt to be precise.
2917                 */
2918                 strncpy(bp, cont_seq, cmatch);
2919                 bp += cmatch;
2920                 buf_len += cmatch;
2921                 cmatch = 0;
2922             }
2923         }
2924
2925         // copy this char
2926         *bp++ = data[i];
2927         buf_len++;
2928     }
2929
2930         buf[buf_len] = NULLCHAR;
2931 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2932         next_out = 0;
2933         leftover_start = 0;
2934
2935         i = 0;
2936         while (i < buf_len) {
2937             /* Deal with part of the TELNET option negotiation
2938                protocol.  We refuse to do anything beyond the
2939                defaults, except that we allow the WILL ECHO option,
2940                which ICS uses to turn off password echoing when we are
2941                directly connected to it.  We reject this option
2942                if localLineEditing mode is on (always on in xboard)
2943                and we are talking to port 23, which might be a real
2944                telnet server that will try to keep WILL ECHO on permanently.
2945              */
2946             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2947                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2948                 unsigned char option;
2949                 oldi = i;
2950                 switch ((unsigned char) buf[++i]) {
2951                   case TN_WILL:
2952                     if (appData.debugMode)
2953                       fprintf(debugFP, "\n<WILL ");
2954                     switch (option = (unsigned char) buf[++i]) {
2955                       case TN_ECHO:
2956                         if (appData.debugMode)
2957                           fprintf(debugFP, "ECHO ");
2958                         /* Reply only if this is a change, according
2959                            to the protocol rules. */
2960                         if (remoteEchoOption) break;
2961                         if (appData.localLineEditing &&
2962                             atoi(appData.icsPort) == TN_PORT) {
2963                             TelnetRequest(TN_DONT, TN_ECHO);
2964                         } else {
2965                             EchoOff();
2966                             TelnetRequest(TN_DO, TN_ECHO);
2967                             remoteEchoOption = TRUE;
2968                         }
2969                         break;
2970                       default:
2971                         if (appData.debugMode)
2972                           fprintf(debugFP, "%d ", option);
2973                         /* Whatever this is, we don't want it. */
2974                         TelnetRequest(TN_DONT, option);
2975                         break;
2976                     }
2977                     break;
2978                   case TN_WONT:
2979                     if (appData.debugMode)
2980                       fprintf(debugFP, "\n<WONT ");
2981                     switch (option = (unsigned char) buf[++i]) {
2982                       case TN_ECHO:
2983                         if (appData.debugMode)
2984                           fprintf(debugFP, "ECHO ");
2985                         /* Reply only if this is a change, according
2986                            to the protocol rules. */
2987                         if (!remoteEchoOption) break;
2988                         EchoOn();
2989                         TelnetRequest(TN_DONT, TN_ECHO);
2990                         remoteEchoOption = FALSE;
2991                         break;
2992                       default:
2993                         if (appData.debugMode)
2994                           fprintf(debugFP, "%d ", (unsigned char) option);
2995                         /* Whatever this is, it must already be turned
2996                            off, because we never agree to turn on
2997                            anything non-default, so according to the
2998                            protocol rules, we don't reply. */
2999                         break;
3000                     }
3001                     break;
3002                   case TN_DO:
3003                     if (appData.debugMode)
3004                       fprintf(debugFP, "\n<DO ");
3005                     switch (option = (unsigned char) buf[++i]) {
3006                       default:
3007                         /* Whatever this is, we refuse to do it. */
3008                         if (appData.debugMode)
3009                           fprintf(debugFP, "%d ", option);
3010                         TelnetRequest(TN_WONT, option);
3011                         break;
3012                     }
3013                     break;
3014                   case TN_DONT:
3015                     if (appData.debugMode)
3016                       fprintf(debugFP, "\n<DONT ");
3017                     switch (option = (unsigned char) buf[++i]) {
3018                       default:
3019                         if (appData.debugMode)
3020                           fprintf(debugFP, "%d ", option);
3021                         /* Whatever this is, we are already not doing
3022                            it, because we never agree to do anything
3023                            non-default, so according to the protocol
3024                            rules, we don't reply. */
3025                         break;
3026                     }
3027                     break;
3028                   case TN_IAC:
3029                     if (appData.debugMode)
3030                       fprintf(debugFP, "\n<IAC ");
3031                     /* Doubled IAC; pass it through */
3032                     i--;
3033                     break;
3034                   default:
3035                     if (appData.debugMode)
3036                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3037                     /* Drop all other telnet commands on the floor */
3038                     break;
3039                 }
3040                 if (oldi > next_out)
3041                   SendToPlayer(&buf[next_out], oldi - next_out);
3042                 if (++i > next_out)
3043                   next_out = i;
3044                 continue;
3045             }
3046
3047             /* OK, this at least will *usually* work */
3048             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3049                 loggedOn = TRUE;
3050             }
3051
3052             if (loggedOn && !intfSet) {
3053                 if (ics_type == ICS_ICC) {
3054                   snprintf(str, MSG_SIZ,
3055                           "/set-quietly interface %s\n/set-quietly style 12\n",
3056                           programVersion);
3057                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3058                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3059                 } else if (ics_type == ICS_CHESSNET) {
3060                   snprintf(str, MSG_SIZ, "/style 12\n");
3061                 } else {
3062                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3063                   strcat(str, programVersion);
3064                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3065                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3066                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3067 #ifdef WIN32
3068                   strcat(str, "$iset nohighlight 1\n");
3069 #endif
3070                   strcat(str, "$iset lock 1\n$style 12\n");
3071                 }
3072                 SendToICS(str);
3073                 NotifyFrontendLogin();
3074                 intfSet = TRUE;
3075             }
3076
3077             if (started == STARTED_COMMENT) {
3078                 /* Accumulate characters in comment */
3079                 parse[parse_pos++] = buf[i];
3080                 if (buf[i] == '\n') {
3081                     parse[parse_pos] = NULLCHAR;
3082                     if(chattingPartner>=0) {
3083                         char mess[MSG_SIZ];
3084                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3085                         OutputChatMessage(chattingPartner, mess);
3086                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3087                             int p;
3088                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3089                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3090                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3091                                 OutputChatMessage(p, mess);
3092                                 break;
3093                             }
3094                         }
3095                         chattingPartner = -1;
3096                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3097                         collective = 0;
3098                     } else
3099                     if(!suppressKibitz) // [HGM] kibitz
3100                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3101                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3102                         int nrDigit = 0, nrAlph = 0, j;
3103                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3104                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3105                         parse[parse_pos] = NULLCHAR;
3106                         // try to be smart: if it does not look like search info, it should go to
3107                         // ICS interaction window after all, not to engine-output window.
3108                         for(j=0; j<parse_pos; j++) { // count letters and digits
3109                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3110                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3111                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3112                         }
3113                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3114                             int depth=0; float score;
3115                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3116                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3117                                 pvInfoList[forwardMostMove-1].depth = depth;
3118                                 pvInfoList[forwardMostMove-1].score = 100*score;
3119                             }
3120                             OutputKibitz(suppressKibitz, parse);
3121                         } else {
3122                             char tmp[MSG_SIZ];
3123                             if(gameMode == IcsObserving) // restore original ICS messages
3124                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3125                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3126                             else
3127                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3129                             SendToPlayer(tmp, strlen(tmp));
3130                         }
3131                         next_out = i+1; // [HGM] suppress printing in ICS window
3132                     }
3133                     started = STARTED_NONE;
3134                 } else {
3135                     /* Don't match patterns against characters in comment */
3136                     i++;
3137                     continue;
3138                 }
3139             }
3140             if (started == STARTED_CHATTER) {
3141                 if (buf[i] != '\n') {
3142                     /* Don't match patterns against characters in chatter */
3143                     i++;
3144                     continue;
3145                 }
3146                 started = STARTED_NONE;
3147                 if(suppressKibitz) next_out = i+1;
3148             }
3149
3150             /* Kludge to deal with rcmd protocol */
3151             if (firstTime && looking_at(buf, &i, "\001*")) {
3152                 DisplayFatalError(&buf[1], 0, 1);
3153                 continue;
3154             } else {
3155                 firstTime = FALSE;
3156             }
3157
3158             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3159                 ics_type = ICS_ICC;
3160                 ics_prefix = "/";
3161                 if (appData.debugMode)
3162                   fprintf(debugFP, "ics_type %d\n", ics_type);
3163                 continue;
3164             }
3165             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3166                 ics_type = ICS_FICS;
3167                 ics_prefix = "$";
3168                 if (appData.debugMode)
3169                   fprintf(debugFP, "ics_type %d\n", ics_type);
3170                 continue;
3171             }
3172             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3173                 ics_type = ICS_CHESSNET;
3174                 ics_prefix = "/";
3175                 if (appData.debugMode)
3176                   fprintf(debugFP, "ics_type %d\n", ics_type);
3177                 continue;
3178             }
3179
3180             if (!loggedOn &&
3181                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3182                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3183                  looking_at(buf, &i, "will be \"*\""))) {
3184               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3185               continue;
3186             }
3187
3188             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3189               char buf[MSG_SIZ];
3190               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3191               DisplayIcsInteractionTitle(buf);
3192               have_set_title = TRUE;
3193             }
3194
3195             /* skip finger notes */
3196             if (started == STARTED_NONE &&
3197                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3198                  (buf[i] == '1' && buf[i+1] == '0')) &&
3199                 buf[i+2] == ':' && buf[i+3] == ' ') {
3200               started = STARTED_CHATTER;
3201               i += 3;
3202               continue;
3203             }
3204
3205             oldi = i;
3206             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3207             if(appData.seekGraph) {
3208                 if(soughtPending && MatchSoughtLine(buf+i)) {
3209                     i = strstr(buf+i, "rated") - buf;
3210                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3211                     next_out = leftover_start = i;
3212                     started = STARTED_CHATTER;
3213                     suppressKibitz = TRUE;
3214                     continue;
3215                 }
3216                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3217                         && looking_at(buf, &i, "* ads displayed")) {
3218                     soughtPending = FALSE;
3219                     seekGraphUp = TRUE;
3220                     DrawSeekGraph();
3221                     continue;
3222                 }
3223                 if(appData.autoRefresh) {
3224                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3225                         int s = (ics_type == ICS_ICC); // ICC format differs
3226                         if(seekGraphUp)
3227                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3228                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3229                         looking_at(buf, &i, "*% "); // eat prompt
3230                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3231                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3232                         next_out = i; // suppress
3233                         continue;
3234                     }
3235                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3236                         char *p = star_match[0];
3237                         while(*p) {
3238                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3239                             while(*p && *p++ != ' '); // next
3240                         }
3241                         looking_at(buf, &i, "*% "); // eat prompt
3242                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                         next_out = i;
3244                         continue;
3245                     }
3246                 }
3247             }
3248
3249             /* skip formula vars */
3250             if (started == STARTED_NONE &&
3251                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3252               started = STARTED_CHATTER;
3253               i += 3;
3254               continue;
3255             }
3256
3257             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3258             if (appData.autoKibitz && started == STARTED_NONE &&
3259                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3260                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3261                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3262                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3263                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3264                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3265                         suppressKibitz = TRUE;
3266                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3267                         next_out = i;
3268                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3269                                 && (gameMode == IcsPlayingWhite)) ||
3270                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3271                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3272                             started = STARTED_CHATTER; // own kibitz we simply discard
3273                         else {
3274                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3275                             parse_pos = 0; parse[0] = NULLCHAR;
3276                             savingComment = TRUE;
3277                             suppressKibitz = gameMode != IcsObserving ? 2 :
3278                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3279                         }
3280                         continue;
3281                 } else
3282                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3283                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3284                          && atoi(star_match[0])) {
3285                     // suppress the acknowledgements of our own autoKibitz
3286                     char *p;
3287                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3288                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3289                     SendToPlayer(star_match[0], strlen(star_match[0]));
3290                     if(looking_at(buf, &i, "*% ")) // eat prompt
3291                         suppressKibitz = FALSE;
3292                     next_out = i;
3293                     continue;
3294                 }
3295             } // [HGM] kibitz: end of patch
3296
3297             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3298
3299             // [HGM] chat: intercept tells by users for which we have an open chat window
3300             channel = -1;
3301             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3302                                            looking_at(buf, &i, "* whispers:") ||
3303                                            looking_at(buf, &i, "* kibitzes:") ||
3304                                            looking_at(buf, &i, "* shouts:") ||
3305                                            looking_at(buf, &i, "* c-shouts:") ||
3306                                            looking_at(buf, &i, "--> * ") ||
3307                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3308                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3309                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3310                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3311                 int p;
3312                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3313                 chattingPartner = -1; collective = 0;
3314
3315                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3316                 for(p=0; p<MAX_CHAT; p++) {
3317                     collective = 1;
3318                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3319                     talker[0] = '['; strcat(talker, "] ");
3320                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3321                     chattingPartner = p; break;
3322                     }
3323                 } else
3324                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3325                 for(p=0; p<MAX_CHAT; p++) {
3326                     collective = 1;
3327                     if(!strcmp("kibitzes", chatPartner[p])) {
3328                         talker[0] = '['; strcat(talker, "] ");
3329                         chattingPartner = p; break;
3330                     }
3331                 } else
3332                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3333                 for(p=0; p<MAX_CHAT; p++) {
3334                     collective = 1;
3335                     if(!strcmp("whispers", chatPartner[p])) {
3336                         talker[0] = '['; strcat(talker, "] ");
3337                         chattingPartner = p; break;
3338                     }
3339                 } else
3340                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3341                   if(buf[i-8] == '-' && buf[i-3] == 't')
3342                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3343                     collective = 1;
3344                     if(!strcmp("c-shouts", chatPartner[p])) {
3345                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3346                         chattingPartner = p; break;
3347                     }
3348                   }
3349                   if(chattingPartner < 0)
3350                   for(p=0; p<MAX_CHAT; p++) {
3351                     collective = 1;
3352                     if(!strcmp("shouts", chatPartner[p])) {
3353                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3354                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3355                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3356                         chattingPartner = p; break;
3357                     }
3358                   }
3359                 }
3360                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3361                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3362                     talker[0] = 0;
3363                     Colorize(ColorTell, FALSE);
3364                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3365                     collective |= 2;
3366                     chattingPartner = p; break;
3367                 }
3368                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3369                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3370                     started = STARTED_COMMENT;
3371                     parse_pos = 0; parse[0] = NULLCHAR;
3372                     savingComment = 3 + chattingPartner; // counts as TRUE
3373                     if(collective == 3) i = oldi; else {
3374                         suppressKibitz = TRUE;
3375                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3376                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3377                         continue;
3378                     }
3379                 }
3380             } // [HGM] chat: end of patch
3381
3382           backup = i;
3383             if (appData.zippyTalk || appData.zippyPlay) {
3384                 /* [DM] Backup address for color zippy lines */
3385 #if ZIPPY
3386                if (loggedOn == TRUE)
3387                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3388                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3389 #endif
3390             } // [DM] 'else { ' deleted
3391                 if (
3392                     /* Regular tells and says */
3393                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3394                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3395                     looking_at(buf, &i, "* says: ") ||
3396                     /* Don't color "message" or "messages" output */
3397                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3398                     looking_at(buf, &i, "*. * at *:*: ") ||
3399                     looking_at(buf, &i, "--* (*:*): ") ||
3400                     /* Message notifications (same color as tells) */
3401                     looking_at(buf, &i, "* has left a message ") ||
3402                     looking_at(buf, &i, "* just sent you a message:\n") ||
3403                     /* Whispers and kibitzes */
3404                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3405                     looking_at(buf, &i, "* kibitzes: ") ||
3406                     /* Channel tells */
3407                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3408
3409                   if (tkind == 1 && strchr(star_match[0], ':')) {
3410                       /* Avoid "tells you:" spoofs in channels */
3411                      tkind = 3;
3412                   }
3413                   if (star_match[0][0] == NULLCHAR ||
3414                       strchr(star_match[0], ' ') ||
3415                       (tkind == 3 && strchr(star_match[1], ' '))) {
3416                     /* Reject bogus matches */
3417                     i = oldi;
3418                   } else {
3419                     if (appData.colorize) {
3420                       if (oldi > next_out) {
3421                         SendToPlayer(&buf[next_out], oldi - next_out);
3422                         next_out = oldi;
3423                       }
3424                       switch (tkind) {
3425                       case 1:
3426                         Colorize(ColorTell, FALSE);
3427                         curColor = ColorTell;
3428                         break;
3429                       case 2:
3430                         Colorize(ColorKibitz, FALSE);
3431                         curColor = ColorKibitz;
3432                         break;
3433                       case 3:
3434                         p = strrchr(star_match[1], '(');
3435                         if (p == NULL) {
3436                           p = star_match[1];
3437                         } else {
3438                           p++;
3439                         }
3440                         if (atoi(p) == 1) {
3441                           Colorize(ColorChannel1, FALSE);
3442                           curColor = ColorChannel1;
3443                         } else {
3444                           Colorize(ColorChannel, FALSE);
3445                           curColor = ColorChannel;
3446                         }
3447                         break;
3448                       case 5:
3449                         curColor = ColorNormal;
3450                         break;
3451                       }
3452                     }
3453                     if (started == STARTED_NONE && appData.autoComment &&
3454                         (gameMode == IcsObserving ||
3455                          gameMode == IcsPlayingWhite ||
3456                          gameMode == IcsPlayingBlack)) {
3457                       parse_pos = i - oldi;
3458                       memcpy(parse, &buf[oldi], parse_pos);
3459                       parse[parse_pos] = NULLCHAR;
3460                       started = STARTED_COMMENT;
3461                       savingComment = TRUE;
3462                     } else if(collective != 3) {
3463                       started = STARTED_CHATTER;
3464                       savingComment = FALSE;
3465                     }
3466                     loggedOn = TRUE;
3467                     continue;
3468                   }
3469                 }
3470
3471                 if (looking_at(buf, &i, "* s-shouts: ") ||
3472                     looking_at(buf, &i, "* c-shouts: ")) {
3473                     if (appData.colorize) {
3474                         if (oldi > next_out) {
3475                             SendToPlayer(&buf[next_out], oldi - next_out);
3476                             next_out = oldi;
3477                         }
3478                         Colorize(ColorSShout, FALSE);
3479                         curColor = ColorSShout;
3480                     }
3481                     loggedOn = TRUE;
3482                     started = STARTED_CHATTER;
3483                     continue;
3484                 }
3485
3486                 if (looking_at(buf, &i, "--->")) {
3487                     loggedOn = TRUE;
3488                     continue;
3489                 }
3490
3491                 if (looking_at(buf, &i, "* shouts: ") ||
3492                     looking_at(buf, &i, "--> ")) {
3493                     if (appData.colorize) {
3494                         if (oldi > next_out) {
3495                             SendToPlayer(&buf[next_out], oldi - next_out);
3496                             next_out = oldi;
3497                         }
3498                         Colorize(ColorShout, FALSE);
3499                         curColor = ColorShout;
3500                     }
3501                     loggedOn = TRUE;
3502                     started = STARTED_CHATTER;
3503                     continue;
3504                 }
3505
3506                 if (looking_at( buf, &i, "Challenge:")) {
3507                     if (appData.colorize) {
3508                         if (oldi > next_out) {
3509                             SendToPlayer(&buf[next_out], oldi - next_out);
3510                             next_out = oldi;
3511                         }
3512                         Colorize(ColorChallenge, FALSE);
3513                         curColor = ColorChallenge;
3514                     }
3515                     loggedOn = TRUE;
3516                     continue;
3517                 }
3518
3519                 if (looking_at(buf, &i, "* offers you") ||
3520                     looking_at(buf, &i, "* offers to be") ||
3521                     looking_at(buf, &i, "* would like to") ||
3522                     looking_at(buf, &i, "* requests to") ||
3523                     looking_at(buf, &i, "Your opponent offers") ||
3524                     looking_at(buf, &i, "Your opponent requests")) {
3525
3526                     if (appData.colorize) {
3527                         if (oldi > next_out) {
3528                             SendToPlayer(&buf[next_out], oldi - next_out);
3529                             next_out = oldi;
3530                         }
3531                         Colorize(ColorRequest, FALSE);
3532                         curColor = ColorRequest;
3533                     }
3534                     continue;
3535                 }
3536
3537                 if (looking_at(buf, &i, "* (*) seeking")) {
3538                     if (appData.colorize) {
3539                         if (oldi > next_out) {
3540                             SendToPlayer(&buf[next_out], oldi - next_out);
3541                             next_out = oldi;
3542                         }
3543                         Colorize(ColorSeek, FALSE);
3544                         curColor = ColorSeek;
3545                     }
3546                     continue;
3547             }
3548
3549           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3550
3551             if (looking_at(buf, &i, "\\   ")) {
3552                 if (prevColor != ColorNormal) {
3553                     if (oldi > next_out) {
3554                         SendToPlayer(&buf[next_out], oldi - next_out);
3555                         next_out = oldi;
3556                     }
3557                     Colorize(prevColor, TRUE);
3558                     curColor = prevColor;
3559                 }
3560                 if (savingComment) {
3561                     parse_pos = i - oldi;
3562                     memcpy(parse, &buf[oldi], parse_pos);
3563                     parse[parse_pos] = NULLCHAR;
3564                     started = STARTED_COMMENT;
3565                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3566                         chattingPartner = savingComment - 3; // kludge to remember the box
3567                 } else {
3568                     started = STARTED_CHATTER;
3569                 }
3570                 continue;
3571             }
3572
3573             if (looking_at(buf, &i, "Black Strength :") ||
3574                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3575                 looking_at(buf, &i, "<10>") ||
3576                 looking_at(buf, &i, "#@#")) {
3577                 /* Wrong board style */
3578                 loggedOn = TRUE;
3579                 SendToICS(ics_prefix);
3580                 SendToICS("set style 12\n");
3581                 SendToICS(ics_prefix);
3582                 SendToICS("refresh\n");
3583                 continue;
3584             }
3585
3586             if (looking_at(buf, &i, "login:")) {
3587               if (!have_sent_ICS_logon) {
3588                 if(ICSInitScript())
3589                   have_sent_ICS_logon = 1;
3590                 else // no init script was found
3591                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3592               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3593                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3594               }
3595                 continue;
3596             }
3597
3598             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3599                 (looking_at(buf, &i, "\n<12> ") ||
3600                  looking_at(buf, &i, "<12> "))) {
3601                 loggedOn = TRUE;
3602                 if (oldi > next_out) {
3603                     SendToPlayer(&buf[next_out], oldi - next_out);
3604                 }
3605                 next_out = i;
3606                 started = STARTED_BOARD;
3607                 parse_pos = 0;
3608                 continue;
3609             }
3610
3611             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3612                 looking_at(buf, &i, "<b1> ")) {
3613                 if (oldi > next_out) {
3614                     SendToPlayer(&buf[next_out], oldi - next_out);
3615                 }
3616                 next_out = i;
3617                 started = STARTED_HOLDINGS;
3618                 parse_pos = 0;
3619                 continue;
3620             }
3621
3622             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3623                 loggedOn = TRUE;
3624                 /* Header for a move list -- first line */
3625
3626                 switch (ics_getting_history) {
3627                   case H_FALSE:
3628                     switch (gameMode) {
3629                       case IcsIdle:
3630                       case BeginningOfGame:
3631                         /* User typed "moves" or "oldmoves" while we
3632                            were idle.  Pretend we asked for these
3633                            moves and soak them up so user can step
3634                            through them and/or save them.
3635                            */
3636                         Reset(FALSE, TRUE);
3637                         gameMode = IcsObserving;
3638                         ModeHighlight();
3639                         ics_gamenum = -1;
3640                         ics_getting_history = H_GOT_UNREQ_HEADER;
3641                         break;
3642                       case EditGame: /*?*/
3643                       case EditPosition: /*?*/
3644                         /* Should above feature work in these modes too? */
3645                         /* For now it doesn't */
3646                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3647                         break;
3648                       default:
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                     }
3652                     break;
3653                   case H_REQUESTED:
3654                     /* Is this the right one? */
3655                     if (gameInfo.white && gameInfo.black &&
3656                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3657                         strcmp(gameInfo.black, star_match[2]) == 0) {
3658                         /* All is well */
3659                         ics_getting_history = H_GOT_REQ_HEADER;
3660                     }
3661                     break;
3662                   case H_GOT_REQ_HEADER:
3663                   case H_GOT_UNREQ_HEADER:
3664                   case H_GOT_UNWANTED_HEADER:
3665                   case H_GETTING_MOVES:
3666                     /* Should not happen */
3667                     DisplayError(_("Error gathering move list: two headers"), 0);
3668                     ics_getting_history = H_FALSE;
3669                     break;
3670                 }
3671
3672                 /* Save player ratings into gameInfo if needed */
3673                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3674                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3675                     (gameInfo.whiteRating == -1 ||
3676                      gameInfo.blackRating == -1)) {
3677
3678                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3679                     gameInfo.blackRating = string_to_rating(star_match[3]);
3680                     if (appData.debugMode)
3681                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3682                               gameInfo.whiteRating, gameInfo.blackRating);
3683                 }
3684                 continue;
3685             }
3686
3687             if (looking_at(buf, &i,
3688               "* * match, initial time: * minute*, increment: * second")) {
3689                 /* Header for a move list -- second line */
3690                 /* Initial board will follow if this is a wild game */
3691                 if (gameInfo.event != NULL) free(gameInfo.event);
3692                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3693                 gameInfo.event = StrSave(str);
3694                 /* [HGM] we switched variant. Translate boards if needed. */
3695                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3696                 continue;
3697             }
3698
3699             if (looking_at(buf, &i, "Move  ")) {
3700                 /* Beginning of a move list */
3701                 switch (ics_getting_history) {
3702                   case H_FALSE:
3703                     /* Normally should not happen */
3704                     /* Maybe user hit reset while we were parsing */
3705                     break;
3706                   case H_REQUESTED:
3707                     /* Happens if we are ignoring a move list that is not
3708                      * the one we just requested.  Common if the user
3709                      * tries to observe two games without turning off
3710                      * getMoveList */
3711                     break;
3712                   case H_GETTING_MOVES:
3713                     /* Should not happen */
3714                     DisplayError(_("Error gathering move list: nested"), 0);
3715                     ics_getting_history = H_FALSE;
3716                     break;
3717                   case H_GOT_REQ_HEADER:
3718                     ics_getting_history = H_GETTING_MOVES;
3719                     started = STARTED_MOVES;
3720                     parse_pos = 0;
3721                     if (oldi > next_out) {
3722                         SendToPlayer(&buf[next_out], oldi - next_out);
3723                     }
3724                     break;
3725                   case H_GOT_UNREQ_HEADER:
3726                     ics_getting_history = H_GETTING_MOVES;
3727                     started = STARTED_MOVES_NOHIDE;
3728                     parse_pos = 0;
3729                     break;
3730                   case H_GOT_UNWANTED_HEADER:
3731                     ics_getting_history = H_FALSE;
3732                     break;
3733                 }
3734                 continue;
3735             }
3736
3737             if (looking_at(buf, &i, "% ") ||
3738                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3739                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3740                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3741                     soughtPending = FALSE;
3742                     seekGraphUp = TRUE;
3743                     DrawSeekGraph();
3744                 }
3745                 if(suppressKibitz) next_out = i;
3746                 savingComment = FALSE;
3747                 suppressKibitz = 0;
3748                 switch (started) {
3749                   case STARTED_MOVES:
3750                   case STARTED_MOVES_NOHIDE:
3751                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3752                     parse[parse_pos + i - oldi] = NULLCHAR;
3753                     ParseGameHistory(parse);
3754 #if ZIPPY
3755                     if (appData.zippyPlay && first.initDone) {
3756                         FeedMovesToProgram(&first, forwardMostMove);
3757                         if (gameMode == IcsPlayingWhite) {
3758                             if (WhiteOnMove(forwardMostMove)) {
3759                                 if (first.sendTime) {
3760                                   if (first.useColors) {
3761                                     SendToProgram("black\n", &first);
3762                                   }
3763                                   SendTimeRemaining(&first, TRUE);
3764                                 }
3765                                 if (first.useColors) {
3766                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3767                                 }
3768                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3769                                 first.maybeThinking = TRUE;
3770                             } else {
3771                                 if (first.usePlayother) {
3772                                   if (first.sendTime) {
3773                                     SendTimeRemaining(&first, TRUE);
3774                                   }
3775                                   SendToProgram("playother\n", &first);
3776                                   firstMove = FALSE;
3777                                 } else {
3778                                   firstMove = TRUE;
3779                                 }
3780                             }
3781                         } else if (gameMode == IcsPlayingBlack) {
3782                             if (!WhiteOnMove(forwardMostMove)) {
3783                                 if (first.sendTime) {
3784                                   if (first.useColors) {
3785                                     SendToProgram("white\n", &first);
3786                                   }
3787                                   SendTimeRemaining(&first, FALSE);
3788                                 }
3789                                 if (first.useColors) {
3790                                   SendToProgram("black\n", &first);
3791                                 }
3792                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3793                                 first.maybeThinking = TRUE;
3794                             } else {
3795                                 if (first.usePlayother) {
3796                                   if (first.sendTime) {
3797                                     SendTimeRemaining(&first, FALSE);
3798                                   }
3799                                   SendToProgram("playother\n", &first);
3800                                   firstMove = FALSE;
3801                                 } else {
3802                                   firstMove = TRUE;
3803                                 }
3804                             }
3805                         }
3806                     }
3807 #endif
3808                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3809                         /* Moves came from oldmoves or moves command
3810                            while we weren't doing anything else.
3811                            */
3812                         currentMove = forwardMostMove;
3813                         ClearHighlights();/*!!could figure this out*/
3814                         flipView = appData.flipView;
3815                         DrawPosition(TRUE, boards[currentMove]);
3816                         DisplayBothClocks();
3817                         snprintf(str, MSG_SIZ, "%s %s %s",
3818                                 gameInfo.white, _("vs."),  gameInfo.black);
3819                         DisplayTitle(str);
3820                         gameMode = IcsIdle;
3821                     } else {
3822                         /* Moves were history of an active game */
3823                         if (gameInfo.resultDetails != NULL) {
3824                             free(gameInfo.resultDetails);
3825                             gameInfo.resultDetails = NULL;
3826                         }
3827                     }
3828                     HistorySet(parseList, backwardMostMove,
3829                                forwardMostMove, currentMove-1);
3830                     DisplayMove(currentMove - 1);
3831                     if (started == STARTED_MOVES) next_out = i;
3832                     started = STARTED_NONE;
3833                     ics_getting_history = H_FALSE;
3834                     break;
3835
3836                   case STARTED_OBSERVE:
3837                     started = STARTED_NONE;
3838                     SendToICS(ics_prefix);
3839                     SendToICS("refresh\n");
3840                     break;
3841
3842                   default:
3843                     break;
3844                 }
3845                 if(bookHit) { // [HGM] book: simulate book reply
3846                     static char bookMove[MSG_SIZ]; // a bit generous?
3847
3848                     programStats.nodes = programStats.depth = programStats.time =
3849                     programStats.score = programStats.got_only_move = 0;
3850                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3851
3852                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3853                     strcat(bookMove, bookHit);
3854                     HandleMachineMove(bookMove, &first);
3855                 }
3856                 continue;
3857             }
3858
3859             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3860                  started == STARTED_HOLDINGS ||
3861                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3862                 /* Accumulate characters in move list or board */
3863                 parse[parse_pos++] = buf[i];
3864             }
3865
3866             /* Start of game messages.  Mostly we detect start of game
3867                when the first board image arrives.  On some versions
3868                of the ICS, though, we need to do a "refresh" after starting
3869                to observe in order to get the current board right away. */
3870             if (looking_at(buf, &i, "Adding game * to observation list")) {
3871                 started = STARTED_OBSERVE;
3872                 continue;
3873             }
3874
3875             /* Handle auto-observe */
3876             if (appData.autoObserve &&
3877                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3878                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3879                 char *player;
3880                 /* Choose the player that was highlighted, if any. */
3881                 if (star_match[0][0] == '\033' ||
3882                     star_match[1][0] != '\033') {
3883                     player = star_match[0];
3884                 } else {
3885                     player = star_match[2];
3886                 }
3887                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3888                         ics_prefix, StripHighlightAndTitle(player));
3889                 SendToICS(str);
3890
3891                 /* Save ratings from notify string */
3892                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3893                 player1Rating = string_to_rating(star_match[1]);
3894                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3895                 player2Rating = string_to_rating(star_match[3]);
3896
3897                 if (appData.debugMode)
3898                   fprintf(debugFP,
3899                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3900                           player1Name, player1Rating,
3901                           player2Name, player2Rating);
3902
3903                 continue;
3904             }
3905
3906             /* Deal with automatic examine mode after a game,
3907                and with IcsObserving -> IcsExamining transition */
3908             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3909                 looking_at(buf, &i, "has made you an examiner of game *")) {
3910
3911                 int gamenum = atoi(star_match[0]);
3912                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3913                     gamenum == ics_gamenum) {
3914                     /* We were already playing or observing this game;
3915                        no need to refetch history */
3916                     gameMode = IcsExamining;
3917                     if (pausing) {
3918                         pauseExamForwardMostMove = forwardMostMove;
3919                     } else if (currentMove < forwardMostMove) {
3920                         ForwardInner(forwardMostMove);
3921                     }
3922                 } else {
3923                     /* I don't think this case really can happen */
3924                     SendToICS(ics_prefix);
3925                     SendToICS("refresh\n");
3926                 }
3927                 continue;
3928             }
3929
3930             /* Error messages */
3931 //          if (ics_user_moved) {
3932             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3933                 if (looking_at(buf, &i, "Illegal move") ||
3934                     looking_at(buf, &i, "Not a legal move") ||
3935                     looking_at(buf, &i, "Your king is in check") ||
3936                     looking_at(buf, &i, "It isn't your turn") ||
3937                     looking_at(buf, &i, "It is not your move")) {
3938                     /* Illegal move */
3939                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3940                         currentMove = forwardMostMove-1;
3941                         DisplayMove(currentMove - 1); /* before DMError */
3942                         DrawPosition(FALSE, boards[currentMove]);
3943                         SwitchClocks(forwardMostMove-1); // [HGM] race
3944                         DisplayBothClocks();
3945                     }
3946                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3947                     ics_user_moved = 0;
3948                     continue;
3949                 }
3950             }
3951
3952             if (looking_at(buf, &i, "still have time") ||
3953                 looking_at(buf, &i, "not out of time") ||
3954                 looking_at(buf, &i, "either player is out of time") ||
3955                 looking_at(buf, &i, "has timeseal; checking")) {
3956                 /* We must have called his flag a little too soon */
3957                 whiteFlag = blackFlag = FALSE;
3958                 continue;
3959             }
3960
3961             if (looking_at(buf, &i, "added * seconds to") ||
3962                 looking_at(buf, &i, "seconds were added to")) {
3963                 /* Update the clocks */
3964                 SendToICS(ics_prefix);
3965                 SendToICS("refresh\n");
3966                 continue;
3967             }
3968
3969             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3970                 ics_clock_paused = TRUE;
3971                 StopClocks();
3972                 continue;
3973             }
3974
3975             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3976                 ics_clock_paused = FALSE;
3977                 StartClocks();
3978                 continue;
3979             }
3980
3981             /* Grab player ratings from the Creating: message.
3982                Note we have to check for the special case when
3983                the ICS inserts things like [white] or [black]. */
3984             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3985                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3986                 /* star_matches:
3987                    0    player 1 name (not necessarily white)
3988                    1    player 1 rating
3989                    2    empty, white, or black (IGNORED)
3990                    3    player 2 name (not necessarily black)
3991                    4    player 2 rating
3992
3993                    The names/ratings are sorted out when the game
3994                    actually starts (below).
3995                 */
3996                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3997                 player1Rating = string_to_rating(star_match[1]);
3998                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3999                 player2Rating = string_to_rating(star_match[4]);
4000
4001                 if (appData.debugMode)
4002                   fprintf(debugFP,
4003                           "Ratings from 'Creating:' %s %d, %s %d\n",
4004                           player1Name, player1Rating,
4005                           player2Name, player2Rating);
4006
4007                 continue;
4008             }
4009
4010             /* Improved generic start/end-of-game messages */
4011             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4012                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4013                 /* If tkind == 0: */
4014                 /* star_match[0] is the game number */
4015                 /*           [1] is the white player's name */
4016                 /*           [2] is the black player's name */
4017                 /* For end-of-game: */
4018                 /*           [3] is the reason for the game end */
4019                 /*           [4] is a PGN end game-token, preceded by " " */
4020                 /* For start-of-game: */
4021                 /*           [3] begins with "Creating" or "Continuing" */
4022                 /*           [4] is " *" or empty (don't care). */
4023                 int gamenum = atoi(star_match[0]);
4024                 char *whitename, *blackname, *why, *endtoken;
4025                 ChessMove endtype = EndOfFile;
4026
4027                 if (tkind == 0) {
4028                   whitename = star_match[1];
4029                   blackname = star_match[2];
4030                   why = star_match[3];
4031                   endtoken = star_match[4];
4032                 } else {
4033                   whitename = star_match[1];
4034                   blackname = star_match[3];
4035                   why = star_match[5];
4036                   endtoken = star_match[6];
4037                 }
4038
4039                 /* Game start messages */
4040                 if (strncmp(why, "Creating ", 9) == 0 ||
4041                     strncmp(why, "Continuing ", 11) == 0) {
4042                     gs_gamenum = gamenum;
4043                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4044                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4045                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4046 #if ZIPPY
4047                     if (appData.zippyPlay) {
4048                         ZippyGameStart(whitename, blackname);
4049                     }
4050 #endif /*ZIPPY*/
4051                     partnerBoardValid = FALSE; // [HGM] bughouse
4052                     continue;
4053                 }
4054
4055                 /* Game end messages */
4056                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4057                     ics_gamenum != gamenum) {
4058                     continue;
4059                 }
4060                 while (endtoken[0] == ' ') endtoken++;
4061                 switch (endtoken[0]) {
4062                   case '*':
4063                   default:
4064                     endtype = GameUnfinished;
4065                     break;
4066                   case '0':
4067                     endtype = BlackWins;
4068                     break;
4069                   case '1':
4070                     if (endtoken[1] == '/')
4071                       endtype = GameIsDrawn;
4072                     else
4073                       endtype = WhiteWins;
4074                     break;
4075                 }
4076                 GameEnds(endtype, why, GE_ICS);
4077 #if ZIPPY
4078                 if (appData.zippyPlay && first.initDone) {
4079                     ZippyGameEnd(endtype, why);
4080                     if (first.pr == NoProc) {
4081                       /* Start the next process early so that we'll
4082                          be ready for the next challenge */
4083                       StartChessProgram(&first);
4084                     }
4085                     /* Send "new" early, in case this command takes
4086                        a long time to finish, so that we'll be ready
4087                        for the next challenge. */
4088                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4089                     Reset(TRUE, TRUE);
4090                 }
4091 #endif /*ZIPPY*/
4092                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4093                 continue;
4094             }
4095
4096             if (looking_at(buf, &i, "Removing game * from observation") ||
4097                 looking_at(buf, &i, "no longer observing game *") ||
4098                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4099                 if (gameMode == IcsObserving &&
4100                     atoi(star_match[0]) == ics_gamenum)
4101                   {
4102                       /* icsEngineAnalyze */
4103                       if (appData.icsEngineAnalyze) {
4104                             ExitAnalyzeMode();
4105                             ModeHighlight();
4106                       }
4107                       StopClocks();
4108                       gameMode = IcsIdle;
4109                       ics_gamenum = -1;
4110                       ics_user_moved = FALSE;
4111                   }
4112                 continue;
4113             }
4114
4115             if (looking_at(buf, &i, "no longer examining game *")) {
4116                 if (gameMode == IcsExamining &&
4117                     atoi(star_match[0]) == ics_gamenum)
4118                   {
4119                       gameMode = IcsIdle;
4120                       ics_gamenum = -1;
4121                       ics_user_moved = FALSE;
4122                   }
4123                 continue;
4124             }
4125
4126             /* Advance leftover_start past any newlines we find,
4127                so only partial lines can get reparsed */
4128             if (looking_at(buf, &i, "\n")) {
4129                 prevColor = curColor;
4130                 if (curColor != ColorNormal) {
4131                     if (oldi > next_out) {
4132                         SendToPlayer(&buf[next_out], oldi - next_out);
4133                         next_out = oldi;
4134                     }
4135                     Colorize(ColorNormal, FALSE);
4136                     curColor = ColorNormal;
4137                 }
4138                 if (started == STARTED_BOARD) {
4139                     started = STARTED_NONE;
4140                     parse[parse_pos] = NULLCHAR;
4141                     ParseBoard12(parse);
4142                     ics_user_moved = 0;
4143
4144                     /* Send premove here */
4145                     if (appData.premove) {
4146                       char str[MSG_SIZ];
4147                       if (currentMove == 0 &&
4148                           gameMode == IcsPlayingWhite &&
4149                           appData.premoveWhite) {
4150                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4151                         if (appData.debugMode)
4152                           fprintf(debugFP, "Sending premove:\n");
4153                         SendToICS(str);
4154                       } else if (currentMove == 1 &&
4155                                  gameMode == IcsPlayingBlack &&
4156                                  appData.premoveBlack) {
4157                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4158                         if (appData.debugMode)
4159                           fprintf(debugFP, "Sending premove:\n");
4160                         SendToICS(str);
4161                       } else if (gotPremove) {
4162                         gotPremove = 0;
4163                         ClearPremoveHighlights();
4164                         if (appData.debugMode)
4165                           fprintf(debugFP, "Sending premove:\n");
4166                           UserMoveEvent(premoveFromX, premoveFromY,
4167                                         premoveToX, premoveToY,
4168                                         premovePromoChar);
4169                       }
4170                     }
4171
4172                     /* Usually suppress following prompt */
4173                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4174                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4175                         if (looking_at(buf, &i, "*% ")) {
4176                             savingComment = FALSE;
4177                             suppressKibitz = 0;
4178                         }
4179                     }
4180                     next_out = i;
4181                 } else if (started == STARTED_HOLDINGS) {
4182                     int gamenum;
4183                     char new_piece[MSG_SIZ];
4184                     started = STARTED_NONE;
4185                     parse[parse_pos] = NULLCHAR;
4186                     if (appData.debugMode)
4187                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4188                                                         parse, currentMove);
4189                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4190                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4191                         if (gameInfo.variant == VariantNormal) {
4192                           /* [HGM] We seem to switch variant during a game!
4193                            * Presumably no holdings were displayed, so we have
4194                            * to move the position two files to the right to
4195                            * create room for them!
4196                            */
4197                           VariantClass newVariant;
4198                           switch(gameInfo.boardWidth) { // base guess on board width
4199                                 case 9:  newVariant = VariantShogi; break;
4200                                 case 10: newVariant = VariantGreat; break;
4201                                 default: newVariant = VariantCrazyhouse; break;
4202                           }
4203                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4204                           /* Get a move list just to see the header, which
4205                              will tell us whether this is really bug or zh */
4206                           if (ics_getting_history == H_FALSE) {
4207                             ics_getting_history = H_REQUESTED;
4208                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4209                             SendToICS(str);
4210                           }
4211                         }
4212                         new_piece[0] = NULLCHAR;
4213                         sscanf(parse, "game %d white [%s black [%s <- %s",
4214                                &gamenum, white_holding, black_holding,
4215                                new_piece);
4216                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4217                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4218                         /* [HGM] copy holdings to board holdings area */
4219                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4220                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4221                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4222 #if ZIPPY
4223                         if (appData.zippyPlay && first.initDone) {
4224                             ZippyHoldings(white_holding, black_holding,
4225                                           new_piece);
4226                         }
4227 #endif /*ZIPPY*/
4228                         if (tinyLayout || smallLayout) {
4229                             char wh[16], bh[16];
4230                             PackHolding(wh, white_holding);
4231                             PackHolding(bh, black_holding);
4232                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4233                                     gameInfo.white, gameInfo.black);
4234                         } else {
4235                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4236                                     gameInfo.white, white_holding, _("vs."),
4237                                     gameInfo.black, black_holding);
4238                         }
4239                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4240                         DrawPosition(FALSE, boards[currentMove]);
4241                         DisplayTitle(str);
4242                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4243                         sscanf(parse, "game %d white [%s black [%s <- %s",
4244                                &gamenum, white_holding, black_holding,
4245                                new_piece);
4246                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4247                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4248                         /* [HGM] copy holdings to partner-board holdings area */
4249                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4250                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4251                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4252                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4253                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4254                       }
4255                     }
4256                     /* Suppress following prompt */
4257                     if (looking_at(buf, &i, "*% ")) {
4258                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4259                         savingComment = FALSE;
4260                         suppressKibitz = 0;
4261                     }
4262                     next_out = i;
4263                 }
4264                 continue;
4265             }
4266
4267             i++;                /* skip unparsed character and loop back */
4268         }
4269
4270         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4271 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4272 //          SendToPlayer(&buf[next_out], i - next_out);
4273             started != STARTED_HOLDINGS && leftover_start > next_out) {
4274             SendToPlayer(&buf[next_out], leftover_start - next_out);
4275             next_out = i;
4276         }
4277
4278         leftover_len = buf_len - leftover_start;
4279         /* if buffer ends with something we couldn't parse,
4280            reparse it after appending the next read */
4281
4282     } else if (count == 0) {
4283         RemoveInputSource(isr);
4284         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4285     } else {
4286         DisplayFatalError(_("Error reading from ICS"), error, 1);
4287     }
4288 }
4289
4290
4291 /* Board style 12 looks like this:
4292
4293    <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
4294
4295  * The "<12> " is stripped before it gets to this routine.  The two
4296  * trailing 0's (flip state and clock ticking) are later addition, and
4297  * some chess servers may not have them, or may have only the first.
4298  * Additional trailing fields may be added in the future.
4299  */
4300
4301 #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"
4302
4303 #define RELATION_OBSERVING_PLAYED    0
4304 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4305 #define RELATION_PLAYING_MYMOVE      1
4306 #define RELATION_PLAYING_NOTMYMOVE  -1
4307 #define RELATION_EXAMINING           2
4308 #define RELATION_ISOLATED_BOARD     -3
4309 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4310
4311 void
4312 ParseBoard12 (char *string)
4313 {
4314 #if ZIPPY
4315     int i, takeback;
4316     char *bookHit = NULL; // [HGM] book
4317 #endif
4318     GameMode newGameMode;
4319     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4320     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4321     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4322     char to_play, board_chars[200];
4323     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4324     char black[32], white[32];
4325     Board board;
4326     int prevMove = currentMove;
4327     int ticking = 2;
4328     ChessMove moveType;
4329     int fromX, fromY, toX, toY;
4330     char promoChar;
4331     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4332     Boolean weird = FALSE, reqFlag = FALSE;
4333
4334     fromX = fromY = toX = toY = -1;
4335
4336     newGame = FALSE;
4337
4338     if (appData.debugMode)
4339       fprintf(debugFP, "Parsing board: %s\n", string);
4340
4341     move_str[0] = NULLCHAR;
4342     elapsed_time[0] = NULLCHAR;
4343     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4344         int  i = 0, j;
4345         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4346             if(string[i] == ' ') { ranks++; files = 0; }
4347             else files++;
4348             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4349             i++;
4350         }
4351         for(j = 0; j <i; j++) board_chars[j] = string[j];
4352         board_chars[i] = '\0';
4353         string += i + 1;
4354     }
4355     n = sscanf(string, PATTERN, &to_play, &double_push,
4356                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4357                &gamenum, white, black, &relation, &basetime, &increment,
4358                &white_stren, &black_stren, &white_time, &black_time,
4359                &moveNum, str, elapsed_time, move_str, &ics_flip,
4360                &ticking);
4361
4362     if (n < 21) {
4363         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4364         DisplayError(str, 0);
4365         return;
4366     }
4367
4368     /* Convert the move number to internal form */
4369     moveNum = (moveNum - 1) * 2;
4370     if (to_play == 'B') moveNum++;
4371     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4372       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4373                         0, 1);
4374       return;
4375     }
4376
4377     switch (relation) {
4378       case RELATION_OBSERVING_PLAYED:
4379       case RELATION_OBSERVING_STATIC:
4380         if (gamenum == -1) {
4381             /* Old ICC buglet */
4382             relation = RELATION_OBSERVING_STATIC;
4383         }
4384         newGameMode = IcsObserving;
4385         break;
4386       case RELATION_PLAYING_MYMOVE:
4387       case RELATION_PLAYING_NOTMYMOVE:
4388         newGameMode =
4389           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4390             IcsPlayingWhite : IcsPlayingBlack;
4391         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4392         break;
4393       case RELATION_EXAMINING:
4394         newGameMode = IcsExamining;
4395         break;
4396       case RELATION_ISOLATED_BOARD:
4397       default:
4398         /* Just display this board.  If user was doing something else,
4399            we will forget about it until the next board comes. */
4400         newGameMode = IcsIdle;
4401         break;
4402       case RELATION_STARTING_POSITION:
4403         newGameMode = gameMode;
4404         break;
4405     }
4406
4407     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4408         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4409          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4410       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4411       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4412       static int lastBgGame = -1;
4413       char *toSqr;
4414       for (k = 0; k < ranks; k++) {
4415         for (j = 0; j < files; j++)
4416           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4417         if(gameInfo.holdingsWidth > 1) {
4418              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4419              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4420         }
4421       }
4422       CopyBoard(partnerBoard, board);
4423       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4424         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4425         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4426       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4427       if(toSqr = strchr(str, '-')) {
4428         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4429         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4430       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4431       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4432       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4433       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4434       if(twoBoards) {
4435           DisplayWhiteClock(white_time*fac, to_play == 'W');
4436           DisplayBlackClock(black_time*fac, to_play != 'W');
4437           activePartner = to_play;
4438           if(gamenum != lastBgGame) {
4439               char buf[MSG_SIZ];
4440               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4441               DisplayTitle(buf);
4442           }
4443           lastBgGame = gamenum;
4444           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4445                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4446       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4447                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4448       if(!twoBoards) DisplayMessage(partnerStatus, "");
4449         partnerBoardValid = TRUE;
4450       return;
4451     }
4452
4453     if(appData.dualBoard && appData.bgObserve) {
4454         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4455             SendToICS(ics_prefix), SendToICS("pobserve\n");
4456         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4457             char buf[MSG_SIZ];
4458             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4459             SendToICS(buf);
4460         }
4461     }
4462
4463     /* Modify behavior for initial board display on move listing
4464        of wild games.
4465        */
4466     switch (ics_getting_history) {
4467       case H_FALSE:
4468       case H_REQUESTED:
4469         break;
4470       case H_GOT_REQ_HEADER:
4471       case H_GOT_UNREQ_HEADER:
4472         /* This is the initial position of the current game */
4473         gamenum = ics_gamenum;
4474         moveNum = 0;            /* old ICS bug workaround */
4475         if (to_play == 'B') {
4476           startedFromSetupPosition = TRUE;
4477           blackPlaysFirst = TRUE;
4478           moveNum = 1;
4479           if (forwardMostMove == 0) forwardMostMove = 1;
4480           if (backwardMostMove == 0) backwardMostMove = 1;
4481           if (currentMove == 0) currentMove = 1;
4482         }
4483         newGameMode = gameMode;
4484         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4485         break;
4486       case H_GOT_UNWANTED_HEADER:
4487         /* This is an initial board that we don't want */
4488         return;
4489       case H_GETTING_MOVES:
4490         /* Should not happen */
4491         DisplayError(_("Error gathering move list: extra board"), 0);
4492         ics_getting_history = H_FALSE;
4493         return;
4494     }
4495
4496    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4497                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4498                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4499      /* [HGM] We seem to have switched variant unexpectedly
4500       * Try to guess new variant from board size
4501       */
4502           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4503           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4504           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4505           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4506           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4507           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4508           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4509           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4510           /* Get a move list just to see the header, which
4511              will tell us whether this is really bug or zh */
4512           if (ics_getting_history == H_FALSE) {
4513             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4514             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4515             SendToICS(str);
4516           }
4517     }
4518
4519     /* Take action if this is the first board of a new game, or of a
4520        different game than is currently being displayed.  */
4521     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4522         relation == RELATION_ISOLATED_BOARD) {
4523
4524         /* Forget the old game and get the history (if any) of the new one */
4525         if (gameMode != BeginningOfGame) {
4526           Reset(TRUE, TRUE);
4527         }
4528         newGame = TRUE;
4529         if (appData.autoRaiseBoard) BoardToTop();
4530         prevMove = -3;
4531         if (gamenum == -1) {
4532             newGameMode = IcsIdle;
4533         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4534                    appData.getMoveList && !reqFlag) {
4535             /* Need to get game history */
4536             ics_getting_history = H_REQUESTED;
4537             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4538             SendToICS(str);
4539         }
4540
4541         /* Initially flip the board to have black on the bottom if playing
4542            black or if the ICS flip flag is set, but let the user change
4543            it with the Flip View button. */
4544         flipView = appData.autoFlipView ?
4545           (newGameMode == IcsPlayingBlack) || ics_flip :
4546           appData.flipView;
4547
4548         /* Done with values from previous mode; copy in new ones */
4549         gameMode = newGameMode;
4550         ModeHighlight();
4551         ics_gamenum = gamenum;
4552         if (gamenum == gs_gamenum) {
4553             int klen = strlen(gs_kind);
4554             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4555             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4556             gameInfo.event = StrSave(str);
4557         } else {
4558             gameInfo.event = StrSave("ICS game");
4559         }
4560         gameInfo.site = StrSave(appData.icsHost);
4561         gameInfo.date = PGNDate();
4562         gameInfo.round = StrSave("-");
4563         gameInfo.white = StrSave(white);
4564         gameInfo.black = StrSave(black);
4565         timeControl = basetime * 60 * 1000;
4566         timeControl_2 = 0;
4567         timeIncrement = increment * 1000;
4568         movesPerSession = 0;
4569         gameInfo.timeControl = TimeControlTagValue();
4570         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4571   if (appData.debugMode) {
4572     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4573     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4574     setbuf(debugFP, NULL);
4575   }
4576
4577         gameInfo.outOfBook = NULL;
4578
4579         /* Do we have the ratings? */
4580         if (strcmp(player1Name, white) == 0 &&
4581             strcmp(player2Name, black) == 0) {
4582             if (appData.debugMode)
4583               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4584                       player1Rating, player2Rating);
4585             gameInfo.whiteRating = player1Rating;
4586             gameInfo.blackRating = player2Rating;
4587         } else if (strcmp(player2Name, white) == 0 &&
4588                    strcmp(player1Name, black) == 0) {
4589             if (appData.debugMode)
4590               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4591                       player2Rating, player1Rating);
4592             gameInfo.whiteRating = player2Rating;
4593             gameInfo.blackRating = player1Rating;
4594         }
4595         player1Name[0] = player2Name[0] = NULLCHAR;
4596
4597         /* Silence shouts if requested */
4598         if (appData.quietPlay &&
4599             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4600             SendToICS(ics_prefix);
4601             SendToICS("set shout 0\n");
4602         }
4603     }
4604
4605     /* Deal with midgame name changes */
4606     if (!newGame) {
4607         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4608             if (gameInfo.white) free(gameInfo.white);
4609             gameInfo.white = StrSave(white);
4610         }
4611         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4612             if (gameInfo.black) free(gameInfo.black);
4613             gameInfo.black = StrSave(black);
4614         }
4615     }
4616
4617     /* Throw away game result if anything actually changes in examine mode */
4618     if (gameMode == IcsExamining && !newGame) {
4619         gameInfo.result = GameUnfinished;
4620         if (gameInfo.resultDetails != NULL) {
4621             free(gameInfo.resultDetails);
4622             gameInfo.resultDetails = NULL;
4623         }
4624     }
4625
4626     /* In pausing && IcsExamining mode, we ignore boards coming
4627        in if they are in a different variation than we are. */
4628     if (pauseExamInvalid) return;
4629     if (pausing && gameMode == IcsExamining) {
4630         if (moveNum <= pauseExamForwardMostMove) {
4631             pauseExamInvalid = TRUE;
4632             forwardMostMove = pauseExamForwardMostMove;
4633             return;
4634         }
4635     }
4636
4637   if (appData.debugMode) {
4638     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4639   }
4640     /* Parse the board */
4641     for (k = 0; k < ranks; k++) {
4642       for (j = 0; j < files; j++)
4643         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4644       if(gameInfo.holdingsWidth > 1) {
4645            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4646            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4647       }
4648     }
4649     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4650       board[5][BOARD_RGHT+1] = WhiteAngel;
4651       board[6][BOARD_RGHT+1] = WhiteMarshall;
4652       board[1][0] = BlackMarshall;
4653       board[2][0] = BlackAngel;
4654       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4655     }
4656     CopyBoard(boards[moveNum], board);
4657     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4658     if (moveNum == 0) {
4659         startedFromSetupPosition =
4660           !CompareBoards(board, initialPosition);
4661         if(startedFromSetupPosition)
4662             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4663     }
4664
4665     /* [HGM] Set castling rights. Take the outermost Rooks,
4666        to make it also work for FRC opening positions. Note that board12
4667        is really defective for later FRC positions, as it has no way to
4668        indicate which Rook can castle if they are on the same side of King.
4669        For the initial position we grant rights to the outermost Rooks,
4670        and remember thos rights, and we then copy them on positions
4671        later in an FRC game. This means WB might not recognize castlings with
4672        Rooks that have moved back to their original position as illegal,
4673        but in ICS mode that is not its job anyway.
4674     */
4675     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4676     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4677
4678         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4679             if(board[0][i] == WhiteRook) j = i;
4680         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4681         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4682             if(board[0][i] == WhiteRook) j = i;
4683         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4684         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4685             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4686         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4687         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4689         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4690
4691         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4692         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4693         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4694             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4695         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4696             if(board[BOARD_HEIGHT-1][k] == bKing)
4697                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4698         if(gameInfo.variant == VariantTwoKings) {
4699             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4700             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4701             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4702         }
4703     } else { int r;
4704         r = boards[moveNum][CASTLING][0] = initialRights[0];
4705         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4706         r = boards[moveNum][CASTLING][1] = initialRights[1];
4707         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4708         r = boards[moveNum][CASTLING][3] = initialRights[3];
4709         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4710         r = boards[moveNum][CASTLING][4] = initialRights[4];
4711         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4712         /* wildcastle kludge: always assume King has rights */
4713         r = boards[moveNum][CASTLING][2] = initialRights[2];
4714         r = boards[moveNum][CASTLING][5] = initialRights[5];
4715     }
4716     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4717     boards[moveNum][EP_STATUS] = EP_NONE;
4718     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4719     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4720     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4721
4722
4723     if (ics_getting_history == H_GOT_REQ_HEADER ||
4724         ics_getting_history == H_GOT_UNREQ_HEADER) {
4725         /* This was an initial position from a move list, not
4726            the current position */
4727         return;
4728     }
4729
4730     /* Update currentMove and known move number limits */
4731     newMove = newGame || moveNum > forwardMostMove;
4732
4733     if (newGame) {
4734         forwardMostMove = backwardMostMove = currentMove = moveNum;
4735         if (gameMode == IcsExamining && moveNum == 0) {
4736           /* Workaround for ICS limitation: we are not told the wild
4737              type when starting to examine a game.  But if we ask for
4738              the move list, the move list header will tell us */
4739             ics_getting_history = H_REQUESTED;
4740             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4741             SendToICS(str);
4742         }
4743     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4744                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4745 #if ZIPPY
4746         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4747         /* [HGM] applied this also to an engine that is silently watching        */
4748         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4749             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4750             gameInfo.variant == currentlyInitializedVariant) {
4751           takeback = forwardMostMove - moveNum;
4752           for (i = 0; i < takeback; i++) {
4753             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4754             SendToProgram("undo\n", &first);
4755           }
4756         }
4757 #endif
4758
4759         forwardMostMove = moveNum;
4760         if (!pausing || currentMove > forwardMostMove)
4761           currentMove = forwardMostMove;
4762     } else {
4763         /* New part of history that is not contiguous with old part */
4764         if (pausing && gameMode == IcsExamining) {
4765             pauseExamInvalid = TRUE;
4766             forwardMostMove = pauseExamForwardMostMove;
4767             return;
4768         }
4769         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4770 #if ZIPPY
4771             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4772                 // [HGM] when we will receive the move list we now request, it will be
4773                 // fed to the engine from the first move on. So if the engine is not
4774                 // in the initial position now, bring it there.
4775                 InitChessProgram(&first, 0);
4776             }
4777 #endif
4778             ics_getting_history = H_REQUESTED;
4779             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4780             SendToICS(str);
4781         }
4782         forwardMostMove = backwardMostMove = currentMove = moveNum;
4783     }
4784
4785     /* Update the clocks */
4786     if (strchr(elapsed_time, '.')) {
4787       /* Time is in ms */
4788       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4789       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4790     } else {
4791       /* Time is in seconds */
4792       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4793       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4794     }
4795
4796
4797 #if ZIPPY
4798     if (appData.zippyPlay && newGame &&
4799         gameMode != IcsObserving && gameMode != IcsIdle &&
4800         gameMode != IcsExamining)
4801       ZippyFirstBoard(moveNum, basetime, increment);
4802 #endif
4803
4804     /* Put the move on the move list, first converting
4805        to canonical algebraic form. */
4806     if (moveNum > 0) {
4807   if (appData.debugMode) {
4808     int f = forwardMostMove;
4809     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4810             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4811             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4812     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4813     fprintf(debugFP, "moveNum = %d\n", moveNum);
4814     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4815     setbuf(debugFP, NULL);
4816   }
4817         if (moveNum <= backwardMostMove) {
4818             /* We don't know what the board looked like before
4819                this move.  Punt. */
4820           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4821             strcat(parseList[moveNum - 1], " ");
4822             strcat(parseList[moveNum - 1], elapsed_time);
4823             moveList[moveNum - 1][0] = NULLCHAR;
4824         } else if (strcmp(move_str, "none") == 0) {
4825             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4826             /* Again, we don't know what the board looked like;
4827                this is really the start of the game. */
4828             parseList[moveNum - 1][0] = NULLCHAR;
4829             moveList[moveNum - 1][0] = NULLCHAR;
4830             backwardMostMove = moveNum;
4831             startedFromSetupPosition = TRUE;
4832             fromX = fromY = toX = toY = -1;
4833         } else {
4834           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4835           //                 So we parse the long-algebraic move string in stead of the SAN move
4836           int valid; char buf[MSG_SIZ], *prom;
4837
4838           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4839                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4840           // str looks something like "Q/a1-a2"; kill the slash
4841           if(str[1] == '/')
4842             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4843           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4844           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4845                 strcat(buf, prom); // long move lacks promo specification!
4846           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4847                 if(appData.debugMode)
4848                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4849                 safeStrCpy(move_str, buf, MSG_SIZ);
4850           }
4851           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4852                                 &fromX, &fromY, &toX, &toY, &promoChar)
4853                || ParseOneMove(buf, moveNum - 1, &moveType,
4854                                 &fromX, &fromY, &toX, &toY, &promoChar);
4855           // end of long SAN patch
4856           if (valid) {
4857             (void) CoordsToAlgebraic(boards[moveNum - 1],
4858                                      PosFlags(moveNum - 1),
4859                                      fromY, fromX, toY, toX, promoChar,
4860                                      parseList[moveNum-1]);
4861             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4862               case MT_NONE:
4863               case MT_STALEMATE:
4864               default:
4865                 break;
4866               case MT_CHECK:
4867                 if(!IS_SHOGI(gameInfo.variant))
4868                     strcat(parseList[moveNum - 1], "+");
4869                 break;
4870               case MT_CHECKMATE:
4871               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4872                 strcat(parseList[moveNum - 1], "#");
4873                 break;
4874             }
4875             strcat(parseList[moveNum - 1], " ");
4876             strcat(parseList[moveNum - 1], elapsed_time);
4877             /* currentMoveString is set as a side-effect of ParseOneMove */
4878             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4879             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4880             strcat(moveList[moveNum - 1], "\n");
4881
4882             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4883                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4884               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4885                 ChessSquare old, new = boards[moveNum][k][j];
4886                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4887                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4888                   if(old == new) continue;
4889                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4890                   else if(new == WhiteWazir || new == BlackWazir) {
4891                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4892                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4893                       else boards[moveNum][k][j] = old; // preserve type of Gold
4894                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4895                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4896               }
4897           } else {
4898             /* Move from ICS was illegal!?  Punt. */
4899             if (appData.debugMode) {
4900               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4901               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4902             }
4903             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4904             strcat(parseList[moveNum - 1], " ");
4905             strcat(parseList[moveNum - 1], elapsed_time);
4906             moveList[moveNum - 1][0] = NULLCHAR;
4907             fromX = fromY = toX = toY = -1;
4908           }
4909         }
4910   if (appData.debugMode) {
4911     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4912     setbuf(debugFP, NULL);
4913   }
4914
4915 #if ZIPPY
4916         /* Send move to chess program (BEFORE animating it). */
4917         if (appData.zippyPlay && !newGame && newMove &&
4918            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4919
4920             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4921                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4922                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4923                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4924                             move_str);
4925                     DisplayError(str, 0);
4926                 } else {
4927                     if (first.sendTime) {
4928                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4929                     }
4930                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4931                     if (firstMove && !bookHit) {
4932                         firstMove = FALSE;
4933                         if (first.useColors) {
4934                           SendToProgram(gameMode == IcsPlayingWhite ?
4935                                         "white\ngo\n" :
4936                                         "black\ngo\n", &first);
4937                         } else {
4938                           SendToProgram("go\n", &first);
4939                         }
4940                         first.maybeThinking = TRUE;
4941                     }
4942                 }
4943             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4944               if (moveList[moveNum - 1][0] == NULLCHAR) {
4945                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4946                 DisplayError(str, 0);
4947               } else {
4948                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4949                 SendMoveToProgram(moveNum - 1, &first);
4950               }
4951             }
4952         }
4953 #endif
4954     }
4955
4956     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4957         /* If move comes from a remote source, animate it.  If it
4958            isn't remote, it will have already been animated. */
4959         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4960             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4961         }
4962         if (!pausing && appData.highlightLastMove) {
4963             SetHighlights(fromX, fromY, toX, toY);
4964         }
4965     }
4966
4967     /* Start the clocks */
4968     whiteFlag = blackFlag = FALSE;
4969     appData.clockMode = !(basetime == 0 && increment == 0);
4970     if (ticking == 0) {
4971       ics_clock_paused = TRUE;
4972       StopClocks();
4973     } else if (ticking == 1) {
4974       ics_clock_paused = FALSE;
4975     }
4976     if (gameMode == IcsIdle ||
4977         relation == RELATION_OBSERVING_STATIC ||
4978         relation == RELATION_EXAMINING ||
4979         ics_clock_paused)
4980       DisplayBothClocks();
4981     else
4982       StartClocks();
4983
4984     /* Display opponents and material strengths */
4985     if (gameInfo.variant != VariantBughouse &&
4986         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4987         if (tinyLayout || smallLayout) {
4988             if(gameInfo.variant == VariantNormal)
4989               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4990                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4991                     basetime, increment);
4992             else
4993               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4994                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4995                     basetime, increment, (int) gameInfo.variant);
4996         } else {
4997             if(gameInfo.variant == VariantNormal)
4998               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4999                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5000                     basetime, increment);
5001             else
5002               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5003                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5004                     basetime, increment, VariantName(gameInfo.variant));
5005         }
5006         DisplayTitle(str);
5007   if (appData.debugMode) {
5008     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5009   }
5010     }
5011
5012
5013     /* Display the board */
5014     if (!pausing && !appData.noGUI) {
5015
5016       if (appData.premove)
5017           if (!gotPremove ||
5018              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5019              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5020               ClearPremoveHighlights();
5021
5022       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5023         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5024       DrawPosition(j, boards[currentMove]);
5025
5026       DisplayMove(moveNum - 1);
5027       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5028             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5029               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5030         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5031       }
5032     }
5033
5034     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5035 #if ZIPPY
5036     if(bookHit) { // [HGM] book: simulate book reply
5037         static char bookMove[MSG_SIZ]; // a bit generous?
5038
5039         programStats.nodes = programStats.depth = programStats.time =
5040         programStats.score = programStats.got_only_move = 0;
5041         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5042
5043         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5044         strcat(bookMove, bookHit);
5045         HandleMachineMove(bookMove, &first);
5046     }
5047 #endif
5048 }
5049
5050 void
5051 GetMoveListEvent ()
5052 {
5053     char buf[MSG_SIZ];
5054     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5055         ics_getting_history = H_REQUESTED;
5056         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5057         SendToICS(buf);
5058     }
5059 }
5060
5061 void
5062 SendToBoth (char *msg)
5063 {   // to make it easy to keep two engines in step in dual analysis
5064     SendToProgram(msg, &first);
5065     if(second.analyzing) SendToProgram(msg, &second);
5066 }
5067
5068 void
5069 AnalysisPeriodicEvent (int force)
5070 {
5071     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5072          && !force) || !appData.periodicUpdates)
5073       return;
5074
5075     /* Send . command to Crafty to collect stats */
5076     SendToBoth(".\n");
5077
5078     /* Don't send another until we get a response (this makes
5079        us stop sending to old Crafty's which don't understand
5080        the "." command (sending illegal cmds resets node count & time,
5081        which looks bad)) */
5082     programStats.ok_to_send = 0;
5083 }
5084
5085 void
5086 ics_update_width (int new_width)
5087 {
5088         ics_printf("set width %d\n", new_width);
5089 }
5090
5091 void
5092 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5093 {
5094     char buf[MSG_SIZ];
5095
5096     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5097         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5098             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5099             SendToProgram(buf, cps);
5100             return;
5101         }
5102         // null move in variant where engine does not understand it (for analysis purposes)
5103         SendBoard(cps, moveNum + 1); // send position after move in stead.
5104         return;
5105     }
5106     if (cps->useUsermove) {
5107       SendToProgram("usermove ", cps);
5108     }
5109     if (cps->useSAN) {
5110       char *space;
5111       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5112         int len = space - parseList[moveNum];
5113         memcpy(buf, parseList[moveNum], len);
5114         buf[len++] = '\n';
5115         buf[len] = NULLCHAR;
5116       } else {
5117         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5118       }
5119       SendToProgram(buf, cps);
5120     } else {
5121       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5122         AlphaRank(moveList[moveNum], 4);
5123         SendToProgram(moveList[moveNum], cps);
5124         AlphaRank(moveList[moveNum], 4); // and back
5125       } else
5126       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5127        * the engine. It would be nice to have a better way to identify castle
5128        * moves here. */
5129       if(appData.fischerCastling && cps->useOOCastle) {
5130         int fromX = moveList[moveNum][0] - AAA;
5131         int fromY = moveList[moveNum][1] - ONE;
5132         int toX = moveList[moveNum][2] - AAA;
5133         int toY = moveList[moveNum][3] - ONE;
5134         if((boards[moveNum][fromY][fromX] == WhiteKing
5135             && boards[moveNum][toY][toX] == WhiteRook)
5136            || (boards[moveNum][fromY][fromX] == BlackKing
5137                && boards[moveNum][toY][toX] == BlackRook)) {
5138           if(toX > fromX) SendToProgram("O-O\n", cps);
5139           else SendToProgram("O-O-O\n", cps);
5140         }
5141         else SendToProgram(moveList[moveNum], cps);
5142       } else
5143       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5144           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5145                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5146                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5147                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5148           SendToProgram(buf, cps);
5149       } else
5150       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5151         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5152           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5153           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5154                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5155         } else
5156           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5157                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5158         SendToProgram(buf, cps);
5159       }
5160       else SendToProgram(moveList[moveNum], cps);
5161       /* End of additions by Tord */
5162     }
5163
5164     /* [HGM] setting up the opening has brought engine in force mode! */
5165     /*       Send 'go' if we are in a mode where machine should play. */
5166     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5167         (gameMode == TwoMachinesPlay   ||
5168 #if ZIPPY
5169          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5170 #endif
5171          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5172         SendToProgram("go\n", cps);
5173   if (appData.debugMode) {
5174     fprintf(debugFP, "(extra)\n");
5175   }
5176     }
5177     setboardSpoiledMachineBlack = 0;
5178 }
5179
5180 void
5181 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5182 {
5183     char user_move[MSG_SIZ];
5184     char suffix[4];
5185
5186     if(gameInfo.variant == VariantSChess && promoChar) {
5187         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5188         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5189     } else suffix[0] = NULLCHAR;
5190
5191     switch (moveType) {
5192       default:
5193         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5194                 (int)moveType, fromX, fromY, toX, toY);
5195         DisplayError(user_move + strlen("say "), 0);
5196         break;
5197       case WhiteKingSideCastle:
5198       case BlackKingSideCastle:
5199       case WhiteQueenSideCastleWild:
5200       case BlackQueenSideCastleWild:
5201       /* PUSH Fabien */
5202       case WhiteHSideCastleFR:
5203       case BlackHSideCastleFR:
5204       /* POP Fabien */
5205         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5206         break;
5207       case WhiteQueenSideCastle:
5208       case BlackQueenSideCastle:
5209       case WhiteKingSideCastleWild:
5210       case BlackKingSideCastleWild:
5211       /* PUSH Fabien */
5212       case WhiteASideCastleFR:
5213       case BlackASideCastleFR:
5214       /* POP Fabien */
5215         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5216         break;
5217       case WhiteNonPromotion:
5218       case BlackNonPromotion:
5219         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5220         break;
5221       case WhitePromotion:
5222       case BlackPromotion:
5223         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5224            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5225           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5226                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5227                 PieceToChar(WhiteFerz));
5228         else if(gameInfo.variant == VariantGreat)
5229           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5230                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5231                 PieceToChar(WhiteMan));
5232         else
5233           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5234                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5235                 promoChar);
5236         break;
5237       case WhiteDrop:
5238       case BlackDrop:
5239       drop:
5240         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5241                  ToUpper(PieceToChar((ChessSquare) fromX)),
5242                  AAA + toX, ONE + toY);
5243         break;
5244       case IllegalMove:  /* could be a variant we don't quite understand */
5245         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5246       case NormalMove:
5247       case WhiteCapturesEnPassant:
5248       case BlackCapturesEnPassant:
5249         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5250                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5251         break;
5252     }
5253     SendToICS(user_move);
5254     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5255         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5256 }
5257
5258 void
5259 UploadGameEvent ()
5260 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5261     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5262     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5263     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5264       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5265       return;
5266     }
5267     if(gameMode != IcsExamining) { // is this ever not the case?
5268         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5269
5270         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5271           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5272         } else { // on FICS we must first go to general examine mode
5273           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5274         }
5275         if(gameInfo.variant != VariantNormal) {
5276             // try figure out wild number, as xboard names are not always valid on ICS
5277             for(i=1; i<=36; i++) {
5278               snprintf(buf, MSG_SIZ, "wild/%d", i);
5279                 if(StringToVariant(buf) == gameInfo.variant) break;
5280             }
5281             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5282             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5283             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5284         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5285         SendToICS(ics_prefix);
5286         SendToICS(buf);
5287         if(startedFromSetupPosition || backwardMostMove != 0) {
5288           fen = PositionToFEN(backwardMostMove, NULL, 1);
5289           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5290             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5291             SendToICS(buf);
5292           } else { // FICS: everything has to set by separate bsetup commands
5293             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5294             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5295             SendToICS(buf);
5296             if(!WhiteOnMove(backwardMostMove)) {
5297                 SendToICS("bsetup tomove black\n");
5298             }
5299             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5300             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5301             SendToICS(buf);
5302             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5303             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5304             SendToICS(buf);
5305             i = boards[backwardMostMove][EP_STATUS];
5306             if(i >= 0) { // set e.p.
5307               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5308                 SendToICS(buf);
5309             }
5310             bsetup++;
5311           }
5312         }
5313       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5314             SendToICS("bsetup done\n"); // switch to normal examining.
5315     }
5316     for(i = backwardMostMove; i<last; i++) {
5317         char buf[20];
5318         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5319         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5320             int len = strlen(moveList[i]);
5321             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5322             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5323         }
5324         SendToICS(buf);
5325     }
5326     SendToICS(ics_prefix);
5327     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5328 }
5329
5330 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5331 int legNr = 1;
5332
5333 void
5334 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5335 {
5336     if (rf == DROP_RANK) {
5337       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5338       sprintf(move, "%c@%c%c\n",
5339                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5340     } else {
5341         if (promoChar == 'x' || promoChar == NULLCHAR) {
5342           sprintf(move, "%c%c%c%c\n",
5343                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5344           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5345         } else {
5346             sprintf(move, "%c%c%c%c%c\n",
5347                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5348         }
5349     }
5350 }
5351
5352 void
5353 ProcessICSInitScript (FILE *f)
5354 {
5355     char buf[MSG_SIZ];
5356
5357     while (fgets(buf, MSG_SIZ, f)) {
5358         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5359     }
5360
5361     fclose(f);
5362 }
5363
5364
5365 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5366 int dragging;
5367 static ClickType lastClickType;
5368
5369 int
5370 Partner (ChessSquare *p)
5371 { // change piece into promotion partner if one shogi-promotes to the other
5372   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5373   ChessSquare partner;
5374   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5375   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5376   *p = partner;
5377   return 1;
5378 }
5379
5380 void
5381 Sweep (int step)
5382 {
5383     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5384     static int toggleFlag;
5385     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5386     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5387     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5388     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5389     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5390     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5391     do {
5392         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5393         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5394         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5395         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5396         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5397         if(!step) step = -1;
5398     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5399             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5400             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5401             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5402     if(toX >= 0) {
5403         int victim = boards[currentMove][toY][toX];
5404         boards[currentMove][toY][toX] = promoSweep;
5405         DrawPosition(FALSE, boards[currentMove]);
5406         boards[currentMove][toY][toX] = victim;
5407     } else
5408     ChangeDragPiece(promoSweep);
5409 }
5410
5411 int
5412 PromoScroll (int x, int y)
5413 {
5414   int step = 0;
5415
5416   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5417   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5418   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5419   if(!step) return FALSE;
5420   lastX = x; lastY = y;
5421   if((promoSweep < BlackPawn) == flipView) step = -step;
5422   if(step > 0) selectFlag = 1;
5423   if(!selectFlag) Sweep(step);
5424   return FALSE;
5425 }
5426
5427 void
5428 NextPiece (int step)
5429 {
5430     ChessSquare piece = boards[currentMove][toY][toX];
5431     do {
5432         pieceSweep -= step;
5433         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5434         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5435         if(!step) step = -1;
5436     } while(PieceToChar(pieceSweep) == '.');
5437     boards[currentMove][toY][toX] = pieceSweep;
5438     DrawPosition(FALSE, boards[currentMove]);
5439     boards[currentMove][toY][toX] = piece;
5440 }
5441 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5442 void
5443 AlphaRank (char *move, int n)
5444 {
5445 //    char *p = move, c; int x, y;
5446
5447     if (appData.debugMode) {
5448         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5449     }
5450
5451     if(move[1]=='*' &&
5452        move[2]>='0' && move[2]<='9' &&
5453        move[3]>='a' && move[3]<='x'    ) {
5454         move[1] = '@';
5455         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5456         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5457     } else
5458     if(move[0]>='0' && move[0]<='9' &&
5459        move[1]>='a' && move[1]<='x' &&
5460        move[2]>='0' && move[2]<='9' &&
5461        move[3]>='a' && move[3]<='x'    ) {
5462         /* input move, Shogi -> normal */
5463         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5464         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5465         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5466         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5467     } else
5468     if(move[1]=='@' &&
5469        move[3]>='0' && move[3]<='9' &&
5470        move[2]>='a' && move[2]<='x'    ) {
5471         move[1] = '*';
5472         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5473         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5474     } else
5475     if(
5476        move[0]>='a' && move[0]<='x' &&
5477        move[3]>='0' && move[3]<='9' &&
5478        move[2]>='a' && move[2]<='x'    ) {
5479          /* output move, normal -> Shogi */
5480         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5481         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5482         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5483         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5484         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5485     }
5486     if (appData.debugMode) {
5487         fprintf(debugFP, "   out = '%s'\n", move);
5488     }
5489 }
5490
5491 char yy_textstr[8000];
5492
5493 /* Parser for moves from gnuchess, ICS, or user typein box */
5494 Boolean
5495 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5496 {
5497     int badFrom;
5498     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5499
5500     switch (*moveType) {
5501       case WhitePromotion:
5502       case BlackPromotion:
5503       case WhiteNonPromotion:
5504       case BlackNonPromotion:
5505       case NormalMove:
5506       case FirstLeg:
5507       case WhiteCapturesEnPassant:
5508       case BlackCapturesEnPassant:
5509       case WhiteKingSideCastle:
5510       case WhiteQueenSideCastle:
5511       case BlackKingSideCastle:
5512       case BlackQueenSideCastle:
5513       case WhiteKingSideCastleWild:
5514       case WhiteQueenSideCastleWild:
5515       case BlackKingSideCastleWild:
5516       case BlackQueenSideCastleWild:
5517       /* Code added by Tord: */
5518       case WhiteHSideCastleFR:
5519       case WhiteASideCastleFR:
5520       case BlackHSideCastleFR:
5521       case BlackASideCastleFR:
5522       /* End of code added by Tord */
5523       case IllegalMove:         /* bug or odd chess variant */
5524         *fromX = currentMoveString[0] - AAA;
5525         *fromY = currentMoveString[1] - ONE;
5526         *toX = currentMoveString[2] - AAA;
5527         *toY = currentMoveString[3] - ONE;
5528         *promoChar = currentMoveString[4];
5529         badFrom = (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT);
5530         if(currentMoveString[1] == '@') { badFrom = FALSE; *fromX = CharToPiece(currentMoveString[0]); *fromY = DROP_RANK; } // illegal drop
5531         if (badFrom ||
5532             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5533     if (appData.debugMode) {
5534         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5535     }
5536             *fromX = *fromY = *toX = *toY = 0;
5537             return FALSE;
5538         }
5539         if (appData.testLegality) {
5540           return (*moveType != IllegalMove);
5541         } else {
5542           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5543                          // [HGM] lion: if this is a double move we are less critical
5544                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5545         }
5546
5547       case WhiteDrop:
5548       case BlackDrop:
5549         *fromX = *moveType == WhiteDrop ?
5550           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5551           (int) CharToPiece(ToLower(currentMoveString[0]));
5552         *fromY = DROP_RANK;
5553         *toX = currentMoveString[2] - AAA;
5554         *toY = currentMoveString[3] - ONE;
5555         *promoChar = NULLCHAR;
5556         return TRUE;
5557
5558       case AmbiguousMove:
5559       case ImpossibleMove:
5560       case EndOfFile:
5561       case ElapsedTime:
5562       case Comment:
5563       case PGNTag:
5564       case NAG:
5565       case WhiteWins:
5566       case BlackWins:
5567       case GameIsDrawn:
5568       default:
5569     if (appData.debugMode) {
5570         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5571     }
5572         /* bug? */
5573         *fromX = *fromY = *toX = *toY = 0;
5574         *promoChar = NULLCHAR;
5575         return FALSE;
5576     }
5577 }
5578
5579 Boolean pushed = FALSE;
5580 char *lastParseAttempt;
5581
5582 void
5583 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5584 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5585   int fromX, fromY, toX, toY; char promoChar;
5586   ChessMove moveType;
5587   Boolean valid;
5588   int nr = 0;
5589
5590   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5591   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5592     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5593     pushed = TRUE;
5594   }
5595   endPV = forwardMostMove;
5596   do {
5597     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5598     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5599     lastParseAttempt = pv;
5600     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5601     if(!valid && nr == 0 &&
5602        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5603         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5604         // Hande case where played move is different from leading PV move
5605         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5606         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5607         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5608         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5609           endPV += 2; // if position different, keep this
5610           moveList[endPV-1][0] = fromX + AAA;
5611           moveList[endPV-1][1] = fromY + ONE;
5612           moveList[endPV-1][2] = toX + AAA;
5613           moveList[endPV-1][3] = toY + ONE;
5614           parseList[endPV-1][0] = NULLCHAR;
5615           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5616         }
5617       }
5618     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5619     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5620     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5621     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5622         valid++; // allow comments in PV
5623         continue;
5624     }
5625     nr++;
5626     if(endPV+1 > framePtr) break; // no space, truncate
5627     if(!valid) break;
5628     endPV++;
5629     CopyBoard(boards[endPV], boards[endPV-1]);
5630     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5631     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5632     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5633     CoordsToAlgebraic(boards[endPV - 1],
5634                              PosFlags(endPV - 1),
5635                              fromY, fromX, toY, toX, promoChar,
5636                              parseList[endPV - 1]);
5637   } while(valid);
5638   if(atEnd == 2) return; // used hidden, for PV conversion
5639   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5640   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5641   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5642                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5643   DrawPosition(TRUE, boards[currentMove]);
5644 }
5645
5646 int
5647 MultiPV (ChessProgramState *cps)
5648 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5649         int i;
5650         for(i=0; i<cps->nrOptions; i++)
5651             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5652                 return i;
5653         return -1;
5654 }
5655
5656 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5657
5658 Boolean
5659 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5660 {
5661         int startPV, multi, lineStart, origIndex = index;
5662         char *p, buf2[MSG_SIZ];
5663         ChessProgramState *cps = (pane ? &second : &first);
5664
5665         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5666         lastX = x; lastY = y;
5667         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5668         lineStart = startPV = index;
5669         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5670         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5671         index = startPV;
5672         do{ while(buf[index] && buf[index] != '\n') index++;
5673         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5674         buf[index] = 0;
5675         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5676                 int n = cps->option[multi].value;
5677                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5678                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5679                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5680                 cps->option[multi].value = n;
5681                 *start = *end = 0;
5682                 return FALSE;
5683         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5684                 ExcludeClick(origIndex - lineStart);
5685                 return FALSE;
5686         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5687                 Collapse(origIndex - lineStart);
5688                 return FALSE;
5689         }
5690         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5691         *start = startPV; *end = index-1;
5692         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5693         return TRUE;
5694 }
5695
5696 char *
5697 PvToSAN (char *pv)
5698 {
5699         static char buf[10*MSG_SIZ];
5700         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5701         *buf = NULLCHAR;
5702         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5703         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5704         for(i = forwardMostMove; i<endPV; i++){
5705             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5706             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5707             k += strlen(buf+k);
5708         }
5709         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5710         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5711         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5712         endPV = savedEnd;
5713         return buf;
5714 }
5715
5716 Boolean
5717 LoadPV (int x, int y)
5718 { // called on right mouse click to load PV
5719   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5720   lastX = x; lastY = y;
5721   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5722   extendGame = FALSE;
5723   return TRUE;
5724 }
5725
5726 void
5727 UnLoadPV ()
5728 {
5729   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5730   if(endPV < 0) return;
5731   if(appData.autoCopyPV) CopyFENToClipboard();
5732   endPV = -1;
5733   if(extendGame && currentMove > forwardMostMove) {
5734         Boolean saveAnimate = appData.animate;
5735         if(pushed) {
5736             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5737                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5738             } else storedGames--; // abandon shelved tail of original game
5739         }
5740         pushed = FALSE;
5741         forwardMostMove = currentMove;
5742         currentMove = oldFMM;
5743         appData.animate = FALSE;
5744         ToNrEvent(forwardMostMove);
5745         appData.animate = saveAnimate;
5746   }
5747   currentMove = forwardMostMove;
5748   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5749   ClearPremoveHighlights();
5750   DrawPosition(TRUE, boards[currentMove]);
5751 }
5752
5753 void
5754 MovePV (int x, int y, int h)
5755 { // step through PV based on mouse coordinates (called on mouse move)
5756   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5757
5758   // we must somehow check if right button is still down (might be released off board!)
5759   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5760   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5761   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5762   if(!step) return;
5763   lastX = x; lastY = y;
5764
5765   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5766   if(endPV < 0) return;
5767   if(y < margin) step = 1; else
5768   if(y > h - margin) step = -1;
5769   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5770   currentMove += step;
5771   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5772   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5773                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5774   DrawPosition(FALSE, boards[currentMove]);
5775 }
5776
5777
5778 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5779 // All positions will have equal probability, but the current method will not provide a unique
5780 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5781 #define DARK 1
5782 #define LITE 2
5783 #define ANY 3
5784
5785 int squaresLeft[4];
5786 int piecesLeft[(int)BlackPawn];
5787 int seed, nrOfShuffles;
5788
5789 void
5790 GetPositionNumber ()
5791 {       // sets global variable seed
5792         int i;
5793
5794         seed = appData.defaultFrcPosition;
5795         if(seed < 0) { // randomize based on time for negative FRC position numbers
5796                 for(i=0; i<50; i++) seed += random();
5797                 seed = random() ^ random() >> 8 ^ random() << 8;
5798                 if(seed<0) seed = -seed;
5799         }
5800 }
5801
5802 int
5803 put (Board board, int pieceType, int rank, int n, int shade)
5804 // put the piece on the (n-1)-th empty squares of the given shade
5805 {
5806         int i;
5807
5808         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5809                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5810                         board[rank][i] = (ChessSquare) pieceType;
5811                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5812                         squaresLeft[ANY]--;
5813                         piecesLeft[pieceType]--;
5814                         return i;
5815                 }
5816         }
5817         return -1;
5818 }
5819
5820
5821 void
5822 AddOnePiece (Board board, int pieceType, int rank, int shade)
5823 // calculate where the next piece goes, (any empty square), and put it there
5824 {
5825         int i;
5826
5827         i = seed % squaresLeft[shade];
5828         nrOfShuffles *= squaresLeft[shade];
5829         seed /= squaresLeft[shade];
5830         put(board, pieceType, rank, i, shade);
5831 }
5832
5833 void
5834 AddTwoPieces (Board board, int pieceType, int rank)
5835 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5836 {
5837         int i, n=squaresLeft[ANY], j=n-1, k;
5838
5839         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5840         i = seed % k;  // pick one
5841         nrOfShuffles *= k;
5842         seed /= k;
5843         while(i >= j) i -= j--;
5844         j = n - 1 - j; i += j;
5845         put(board, pieceType, rank, j, ANY);
5846         put(board, pieceType, rank, i, ANY);
5847 }
5848
5849 void
5850 SetUpShuffle (Board board, int number)
5851 {
5852         int i, p, first=1;
5853
5854         GetPositionNumber(); nrOfShuffles = 1;
5855
5856         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5857         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5858         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5859
5860         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5861
5862         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5863             p = (int) board[0][i];
5864             if(p < (int) BlackPawn) piecesLeft[p] ++;
5865             board[0][i] = EmptySquare;
5866         }
5867
5868         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5869             // shuffles restricted to allow normal castling put KRR first
5870             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5871                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5872             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5873                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5874             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5875                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5876             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5877                 put(board, WhiteRook, 0, 0, ANY);
5878             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5879         }
5880
5881         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5882             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5883             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5884                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5885                 while(piecesLeft[p] >= 2) {
5886                     AddOnePiece(board, p, 0, LITE);
5887                     AddOnePiece(board, p, 0, DARK);
5888                 }
5889                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5890             }
5891
5892         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5893             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5894             // but we leave King and Rooks for last, to possibly obey FRC restriction
5895             if(p == (int)WhiteRook) continue;
5896             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5897             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5898         }
5899
5900         // now everything is placed, except perhaps King (Unicorn) and Rooks
5901
5902         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5903             // Last King gets castling rights
5904             while(piecesLeft[(int)WhiteUnicorn]) {
5905                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5906                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5907             }
5908
5909             while(piecesLeft[(int)WhiteKing]) {
5910                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5911                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5912             }
5913
5914
5915         } else {
5916             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5917             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5918         }
5919
5920         // Only Rooks can be left; simply place them all
5921         while(piecesLeft[(int)WhiteRook]) {
5922                 i = put(board, WhiteRook, 0, 0, ANY);
5923                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5924                         if(first) {
5925                                 first=0;
5926                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5927                         }
5928                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5929                 }
5930         }
5931         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5932             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5933         }
5934
5935         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5936 }
5937
5938 int
5939 SetCharTable (char *table, const char * map)
5940 /* [HGM] moved here from winboard.c because of its general usefulness */
5941 /*       Basically a safe strcpy that uses the last character as King */
5942 {
5943     int result = FALSE; int NrPieces;
5944
5945     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5946                     && NrPieces >= 12 && !(NrPieces&1)) {
5947         int i; /* [HGM] Accept even length from 12 to 34 */
5948
5949         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5950         for( i=0; i<NrPieces/2-1; i++ ) {
5951             table[i] = map[i];
5952             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5953         }
5954         table[(int) WhiteKing]  = map[NrPieces/2-1];
5955         table[(int) BlackKing]  = map[NrPieces-1];
5956
5957         result = TRUE;
5958     }
5959
5960     return result;
5961 }
5962
5963 void
5964 Prelude (Board board)
5965 {       // [HGM] superchess: random selection of exo-pieces
5966         int i, j, k; ChessSquare p;
5967         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5968
5969         GetPositionNumber(); // use FRC position number
5970
5971         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5972             SetCharTable(pieceToChar, appData.pieceToCharTable);
5973             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5974                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5975         }
5976
5977         j = seed%4;                 seed /= 4;
5978         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5979         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5980         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5981         j = seed%3 + (seed%3 >= j); seed /= 3;
5982         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5983         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5984         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5985         j = seed%3;                 seed /= 3;
5986         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5987         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5988         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5989         j = seed%2 + (seed%2 >= j); seed /= 2;
5990         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5991         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5992         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5993         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5994         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5995         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5996         put(board, exoPieces[0],    0, 0, ANY);
5997         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5998 }
5999
6000 void
6001 InitPosition (int redraw)
6002 {
6003     ChessSquare (* pieces)[BOARD_FILES];
6004     int i, j, pawnRow=1, pieceRows=1, overrule,
6005     oldx = gameInfo.boardWidth,
6006     oldy = gameInfo.boardHeight,
6007     oldh = gameInfo.holdingsWidth;
6008     static int oldv;
6009
6010     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6011
6012     /* [AS] Initialize pv info list [HGM] and game status */
6013     {
6014         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6015             pvInfoList[i].depth = 0;
6016             boards[i][EP_STATUS] = EP_NONE;
6017             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6018         }
6019
6020         initialRulePlies = 0; /* 50-move counter start */
6021
6022         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6023         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6024     }
6025
6026
6027     /* [HGM] logic here is completely changed. In stead of full positions */
6028     /* the initialized data only consist of the two backranks. The switch */
6029     /* selects which one we will use, which is than copied to the Board   */
6030     /* initialPosition, which for the rest is initialized by Pawns and    */
6031     /* empty squares. This initial position is then copied to boards[0],  */
6032     /* possibly after shuffling, so that it remains available.            */
6033
6034     gameInfo.holdingsWidth = 0; /* default board sizes */
6035     gameInfo.boardWidth    = 8;
6036     gameInfo.boardHeight   = 8;
6037     gameInfo.holdingsSize  = 0;
6038     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6039     for(i=0; i<BOARD_FILES-6; i++)
6040       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6041     initialPosition[EP_STATUS] = EP_NONE;
6042     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6043     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6044     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6045          SetCharTable(pieceNickName, appData.pieceNickNames);
6046     else SetCharTable(pieceNickName, "............");
6047     pieces = FIDEArray;
6048
6049     switch (gameInfo.variant) {
6050     case VariantFischeRandom:
6051       shuffleOpenings = TRUE;
6052       appData.fischerCastling = TRUE;
6053     default:
6054       break;
6055     case VariantShatranj:
6056       pieces = ShatranjArray;
6057       nrCastlingRights = 0;
6058       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6059       break;
6060     case VariantMakruk:
6061       pieces = makrukArray;
6062       nrCastlingRights = 0;
6063       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6064       break;
6065     case VariantASEAN:
6066       pieces = aseanArray;
6067       nrCastlingRights = 0;
6068       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6069       break;
6070     case VariantTwoKings:
6071       pieces = twoKingsArray;
6072       break;
6073     case VariantGrand:
6074       pieces = GrandArray;
6075       nrCastlingRights = 0;
6076       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6077       gameInfo.boardWidth = 10;
6078       gameInfo.boardHeight = 10;
6079       gameInfo.holdingsSize = 7;
6080       break;
6081     case VariantCapaRandom:
6082       shuffleOpenings = TRUE;
6083       appData.fischerCastling = TRUE;
6084     case VariantCapablanca:
6085       pieces = CapablancaArray;
6086       gameInfo.boardWidth = 10;
6087       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6088       break;
6089     case VariantGothic:
6090       pieces = GothicArray;
6091       gameInfo.boardWidth = 10;
6092       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6093       break;
6094     case VariantSChess:
6095       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6096       gameInfo.holdingsSize = 7;
6097       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6098       break;
6099     case VariantJanus:
6100       pieces = JanusArray;
6101       gameInfo.boardWidth = 10;
6102       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6103       nrCastlingRights = 6;
6104         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6105         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6106         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6107         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6108         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6109         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6110       break;
6111     case VariantFalcon:
6112       pieces = FalconArray;
6113       gameInfo.boardWidth = 10;
6114       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6115       break;
6116     case VariantXiangqi:
6117       pieces = XiangqiArray;
6118       gameInfo.boardWidth  = 9;
6119       gameInfo.boardHeight = 10;
6120       nrCastlingRights = 0;
6121       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6122       break;
6123     case VariantShogi:
6124       pieces = ShogiArray;
6125       gameInfo.boardWidth  = 9;
6126       gameInfo.boardHeight = 9;
6127       gameInfo.holdingsSize = 7;
6128       nrCastlingRights = 0;
6129       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6130       break;
6131     case VariantChu:
6132       pieces = ChuArray; pieceRows = 3;
6133       gameInfo.boardWidth  = 12;
6134       gameInfo.boardHeight = 12;
6135       nrCastlingRights = 0;
6136       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6137                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6138       break;
6139     case VariantCourier:
6140       pieces = CourierArray;
6141       gameInfo.boardWidth  = 12;
6142       nrCastlingRights = 0;
6143       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6144       break;
6145     case VariantKnightmate:
6146       pieces = KnightmateArray;
6147       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6148       break;
6149     case VariantSpartan:
6150       pieces = SpartanArray;
6151       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6152       break;
6153     case VariantLion:
6154       pieces = lionArray;
6155       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6156       break;
6157     case VariantChuChess:
6158       pieces = ChuChessArray;
6159       gameInfo.boardWidth = 10;
6160       gameInfo.boardHeight = 10;
6161       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6162       break;
6163     case VariantFairy:
6164       pieces = fairyArray;
6165       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6166       break;
6167     case VariantGreat:
6168       pieces = GreatArray;
6169       gameInfo.boardWidth = 10;
6170       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6171       gameInfo.holdingsSize = 8;
6172       break;
6173     case VariantSuper:
6174       pieces = FIDEArray;
6175       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6176       gameInfo.holdingsSize = 8;
6177       startedFromSetupPosition = TRUE;
6178       break;
6179     case VariantCrazyhouse:
6180     case VariantBughouse:
6181       pieces = FIDEArray;
6182       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6183       gameInfo.holdingsSize = 5;
6184       break;
6185     case VariantWildCastle:
6186       pieces = FIDEArray;
6187       /* !!?shuffle with kings guaranteed to be on d or e file */
6188       shuffleOpenings = 1;
6189       break;
6190     case VariantNoCastle:
6191       pieces = FIDEArray;
6192       nrCastlingRights = 0;
6193       /* !!?unconstrained back-rank shuffle */
6194       shuffleOpenings = 1;
6195       break;
6196     }
6197
6198     overrule = 0;
6199     if(appData.NrFiles >= 0) {
6200         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6201         gameInfo.boardWidth = appData.NrFiles;
6202     }
6203     if(appData.NrRanks >= 0) {
6204         gameInfo.boardHeight = appData.NrRanks;
6205     }
6206     if(appData.holdingsSize >= 0) {
6207         i = appData.holdingsSize;
6208         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6209         gameInfo.holdingsSize = i;
6210     }
6211     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6212     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6213         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6214
6215     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6216     if(pawnRow < 1) pawnRow = 1;
6217     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6218        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6219     if(gameInfo.variant == VariantChu) pawnRow = 3;
6220
6221     /* User pieceToChar list overrules defaults */
6222     if(appData.pieceToCharTable != NULL)
6223         SetCharTable(pieceToChar, appData.pieceToCharTable);
6224
6225     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6226
6227         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6228             s = (ChessSquare) 0; /* account holding counts in guard band */
6229         for( i=0; i<BOARD_HEIGHT; i++ )
6230             initialPosition[i][j] = s;
6231
6232         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6233         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6234         initialPosition[pawnRow][j] = WhitePawn;
6235         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6236         if(gameInfo.variant == VariantXiangqi) {
6237             if(j&1) {
6238                 initialPosition[pawnRow][j] =
6239                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6240                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6241                    initialPosition[2][j] = WhiteCannon;
6242                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6243                 }
6244             }
6245         }
6246         if(gameInfo.variant == VariantChu) {
6247              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6248                initialPosition[pawnRow+1][j] = WhiteCobra,
6249                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6250              for(i=1; i<pieceRows; i++) {
6251                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6252                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6253              }
6254         }
6255         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6256             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6257                initialPosition[0][j] = WhiteRook;
6258                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6259             }
6260         }
6261         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6262     }
6263     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6264     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6265
6266             j=BOARD_LEFT+1;
6267             initialPosition[1][j] = WhiteBishop;
6268             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6269             j=BOARD_RGHT-2;
6270             initialPosition[1][j] = WhiteRook;
6271             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6272     }
6273
6274     if( nrCastlingRights == -1) {
6275         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6276         /*       This sets default castling rights from none to normal corners   */
6277         /* Variants with other castling rights must set them themselves above    */
6278         nrCastlingRights = 6;
6279
6280         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6281         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6282         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6283         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6284         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6285         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6286      }
6287
6288      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6289      if(gameInfo.variant == VariantGreat) { // promotion commoners
6290         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6291         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6292         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6293         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6294      }
6295      if( gameInfo.variant == VariantSChess ) {
6296       initialPosition[1][0] = BlackMarshall;
6297       initialPosition[2][0] = BlackAngel;
6298       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6299       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6300       initialPosition[1][1] = initialPosition[2][1] =
6301       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6302      }
6303   if (appData.debugMode) {
6304     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6305   }
6306     if(shuffleOpenings) {
6307         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6308         startedFromSetupPosition = TRUE;
6309     }
6310     if(startedFromPositionFile) {
6311       /* [HGM] loadPos: use PositionFile for every new game */
6312       CopyBoard(initialPosition, filePosition);
6313       for(i=0; i<nrCastlingRights; i++)
6314           initialRights[i] = filePosition[CASTLING][i];
6315       startedFromSetupPosition = TRUE;
6316     }
6317
6318     CopyBoard(boards[0], initialPosition);
6319
6320     if(oldx != gameInfo.boardWidth ||
6321        oldy != gameInfo.boardHeight ||
6322        oldv != gameInfo.variant ||
6323        oldh != gameInfo.holdingsWidth
6324                                          )
6325             InitDrawingSizes(-2 ,0);
6326
6327     oldv = gameInfo.variant;
6328     if (redraw)
6329       DrawPosition(TRUE, boards[currentMove]);
6330 }
6331
6332 void
6333 SendBoard (ChessProgramState *cps, int moveNum)
6334 {
6335     char message[MSG_SIZ];
6336
6337     if (cps->useSetboard) {
6338       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6339       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6340       SendToProgram(message, cps);
6341       free(fen);
6342
6343     } else {
6344       ChessSquare *bp;
6345       int i, j, left=0, right=BOARD_WIDTH;
6346       /* Kludge to set black to move, avoiding the troublesome and now
6347        * deprecated "black" command.
6348        */
6349       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6350         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6351
6352       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6353
6354       SendToProgram("edit\n", cps);
6355       SendToProgram("#\n", cps);
6356       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6357         bp = &boards[moveNum][i][left];
6358         for (j = left; j < right; j++, bp++) {
6359           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6360           if ((int) *bp < (int) BlackPawn) {
6361             if(j == BOARD_RGHT+1)
6362                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6363             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6364             if(message[0] == '+' || message[0] == '~') {
6365               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6366                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6367                         AAA + j, ONE + i);
6368             }
6369             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6370                 message[1] = BOARD_RGHT   - 1 - j + '1';
6371                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6372             }
6373             SendToProgram(message, cps);
6374           }
6375         }
6376       }
6377
6378       SendToProgram("c\n", cps);
6379       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6380         bp = &boards[moveNum][i][left];
6381         for (j = left; j < right; j++, bp++) {
6382           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6383           if (((int) *bp != (int) EmptySquare)
6384               && ((int) *bp >= (int) BlackPawn)) {
6385             if(j == BOARD_LEFT-2)
6386                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6387             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6388                     AAA + j, ONE + i);
6389             if(message[0] == '+' || message[0] == '~') {
6390               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6391                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6392                         AAA + j, ONE + i);
6393             }
6394             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6395                 message[1] = BOARD_RGHT   - 1 - j + '1';
6396                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6397             }
6398             SendToProgram(message, cps);
6399           }
6400         }
6401       }
6402
6403       SendToProgram(".\n", cps);
6404     }
6405     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6406 }
6407
6408 char exclusionHeader[MSG_SIZ];
6409 int exCnt, excludePtr;
6410 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6411 static Exclusion excluTab[200];
6412 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6413
6414 static void
6415 WriteMap (int s)
6416 {
6417     int j;
6418     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6419     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6420 }
6421
6422 static void
6423 ClearMap ()
6424 {
6425     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6426     excludePtr = 24; exCnt = 0;
6427     WriteMap(0);
6428 }
6429
6430 static void
6431 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6432 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6433     char buf[2*MOVE_LEN], *p;
6434     Exclusion *e = excluTab;
6435     int i;
6436     for(i=0; i<exCnt; i++)
6437         if(e[i].ff == fromX && e[i].fr == fromY &&
6438            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6439     if(i == exCnt) { // was not in exclude list; add it
6440         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6441         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6442             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6443             return; // abort
6444         }
6445         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6446         excludePtr++; e[i].mark = excludePtr++;
6447         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6448         exCnt++;
6449     }
6450     exclusionHeader[e[i].mark] = state;
6451 }
6452
6453 static int
6454 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6455 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6456     char buf[MSG_SIZ];
6457     int j, k;
6458     ChessMove moveType;
6459     if((signed char)promoChar == -1) { // kludge to indicate best move
6460         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6461             return 1; // if unparsable, abort
6462     }
6463     // update exclusion map (resolving toggle by consulting existing state)
6464     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6465     j = k%8; k >>= 3;
6466     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6467     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6468          excludeMap[k] |=   1<<j;
6469     else excludeMap[k] &= ~(1<<j);
6470     // update header
6471     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6472     // inform engine
6473     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6474     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6475     SendToBoth(buf);
6476     return (state == '+');
6477 }
6478
6479 static void
6480 ExcludeClick (int index)
6481 {
6482     int i, j;
6483     Exclusion *e = excluTab;
6484     if(index < 25) { // none, best or tail clicked
6485         if(index < 13) { // none: include all
6486             WriteMap(0); // clear map
6487             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6488             SendToBoth("include all\n"); // and inform engine
6489         } else if(index > 18) { // tail
6490             if(exclusionHeader[19] == '-') { // tail was excluded
6491                 SendToBoth("include all\n");
6492                 WriteMap(0); // clear map completely
6493                 // now re-exclude selected moves
6494                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6495                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6496             } else { // tail was included or in mixed state
6497                 SendToBoth("exclude all\n");
6498                 WriteMap(0xFF); // fill map completely
6499                 // now re-include selected moves
6500                 j = 0; // count them
6501                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6502                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6503                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6504             }
6505         } else { // best
6506             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6507         }
6508     } else {
6509         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6510             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6511             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6512             break;
6513         }
6514     }
6515 }
6516
6517 ChessSquare
6518 DefaultPromoChoice (int white)
6519 {
6520     ChessSquare result;
6521     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6522        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6523         result = WhiteFerz; // no choice
6524     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6525         result= WhiteKing; // in Suicide Q is the last thing we want
6526     else if(gameInfo.variant == VariantSpartan)
6527         result = white ? WhiteQueen : WhiteAngel;
6528     else result = WhiteQueen;
6529     if(!white) result = WHITE_TO_BLACK result;
6530     return result;
6531 }
6532
6533 static int autoQueen; // [HGM] oneclick
6534
6535 int
6536 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6537 {
6538     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6539     /* [HGM] add Shogi promotions */
6540     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6541     ChessSquare piece, partner;
6542     ChessMove moveType;
6543     Boolean premove;
6544
6545     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6546     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6547
6548     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6549       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6550         return FALSE;
6551
6552     piece = boards[currentMove][fromY][fromX];
6553     if(gameInfo.variant == VariantChu) {
6554         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6555         promotionZoneSize = BOARD_HEIGHT/3;
6556         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6557     } else if(gameInfo.variant == VariantShogi) {
6558         promotionZoneSize = BOARD_HEIGHT/3;
6559         highestPromotingPiece = (int)WhiteAlfil;
6560     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6561         promotionZoneSize = 3;
6562     }
6563
6564     // Treat Lance as Pawn when it is not representing Amazon or Lance
6565     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6566         if(piece == WhiteLance) piece = WhitePawn; else
6567         if(piece == BlackLance) piece = BlackPawn;
6568     }
6569
6570     // next weed out all moves that do not touch the promotion zone at all
6571     if((int)piece >= BlackPawn) {
6572         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6573              return FALSE;
6574         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6575         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6576     } else {
6577         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6578            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6579         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6580              return FALSE;
6581     }
6582
6583     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6584
6585     // weed out mandatory Shogi promotions
6586     if(gameInfo.variant == VariantShogi) {
6587         if(piece >= BlackPawn) {
6588             if(toY == 0 && piece == BlackPawn ||
6589                toY == 0 && piece == BlackQueen ||
6590                toY <= 1 && piece == BlackKnight) {
6591                 *promoChoice = '+';
6592                 return FALSE;
6593             }
6594         } else {
6595             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6596                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6597                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6598                 *promoChoice = '+';
6599                 return FALSE;
6600             }
6601         }
6602     }
6603
6604     // weed out obviously illegal Pawn moves
6605     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6606         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6607         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6608         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6609         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6610         // note we are not allowed to test for valid (non-)capture, due to premove
6611     }
6612
6613     // we either have a choice what to promote to, or (in Shogi) whether to promote
6614     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6615        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6616         ChessSquare p=BlackFerz;  // no choice
6617         while(p < EmptySquare) {  //but make sure we use piece that exists
6618             *promoChoice = PieceToChar(p++);
6619             if(*promoChoice != '.') break;
6620         }
6621         return FALSE;
6622     }
6623     // no sense asking what we must promote to if it is going to explode...
6624     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6625         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6626         return FALSE;
6627     }
6628     // give caller the default choice even if we will not make it
6629     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6630     partner = piece; // pieces can promote if the pieceToCharTable says so
6631     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6632     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6633     if(        sweepSelect && gameInfo.variant != VariantGreat
6634                            && gameInfo.variant != VariantGrand
6635                            && gameInfo.variant != VariantSuper) return FALSE;
6636     if(autoQueen) return FALSE; // predetermined
6637
6638     // suppress promotion popup on illegal moves that are not premoves
6639     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6640               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6641     if(appData.testLegality && !premove) {
6642         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6643                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6644         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6645         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6646             return FALSE;
6647     }
6648
6649     return TRUE;
6650 }
6651
6652 int
6653 InPalace (int row, int column)
6654 {   /* [HGM] for Xiangqi */
6655     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6656          column < (BOARD_WIDTH + 4)/2 &&
6657          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6658     return FALSE;
6659 }
6660
6661 int
6662 PieceForSquare (int x, int y)
6663 {
6664   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6665      return -1;
6666   else
6667      return boards[currentMove][y][x];
6668 }
6669
6670 int
6671 OKToStartUserMove (int x, int y)
6672 {
6673     ChessSquare from_piece;
6674     int white_piece;
6675
6676     if (matchMode) return FALSE;
6677     if (gameMode == EditPosition) return TRUE;
6678
6679     if (x >= 0 && y >= 0)
6680       from_piece = boards[currentMove][y][x];
6681     else
6682       from_piece = EmptySquare;
6683
6684     if (from_piece == EmptySquare) return FALSE;
6685
6686     white_piece = (int)from_piece >= (int)WhitePawn &&
6687       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6688
6689     switch (gameMode) {
6690       case AnalyzeFile:
6691       case TwoMachinesPlay:
6692       case EndOfGame:
6693         return FALSE;
6694
6695       case IcsObserving:
6696       case IcsIdle:
6697         return FALSE;
6698
6699       case MachinePlaysWhite:
6700       case IcsPlayingBlack:
6701         if (appData.zippyPlay) return FALSE;
6702         if (white_piece) {
6703             DisplayMoveError(_("You are playing Black"));
6704             return FALSE;
6705         }
6706         break;
6707
6708       case MachinePlaysBlack:
6709       case IcsPlayingWhite:
6710         if (appData.zippyPlay) return FALSE;
6711         if (!white_piece) {
6712             DisplayMoveError(_("You are playing White"));
6713             return FALSE;
6714         }
6715         break;
6716
6717       case PlayFromGameFile:
6718             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6719       case EditGame:
6720         if (!white_piece && WhiteOnMove(currentMove)) {
6721             DisplayMoveError(_("It is White's turn"));
6722             return FALSE;
6723         }
6724         if (white_piece && !WhiteOnMove(currentMove)) {
6725             DisplayMoveError(_("It is Black's turn"));
6726             return FALSE;
6727         }
6728         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6729             /* Editing correspondence game history */
6730             /* Could disallow this or prompt for confirmation */
6731             cmailOldMove = -1;
6732         }
6733         break;
6734
6735       case BeginningOfGame:
6736         if (appData.icsActive) return FALSE;
6737         if (!appData.noChessProgram) {
6738             if (!white_piece) {
6739                 DisplayMoveError(_("You are playing White"));
6740                 return FALSE;
6741             }
6742         }
6743         break;
6744
6745       case Training:
6746         if (!white_piece && WhiteOnMove(currentMove)) {
6747             DisplayMoveError(_("It is White's turn"));
6748             return FALSE;
6749         }
6750         if (white_piece && !WhiteOnMove(currentMove)) {
6751             DisplayMoveError(_("It is Black's turn"));
6752             return FALSE;
6753         }
6754         break;
6755
6756       default:
6757       case IcsExamining:
6758         break;
6759     }
6760     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6761         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6762         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6763         && gameMode != AnalyzeFile && gameMode != Training) {
6764         DisplayMoveError(_("Displayed position is not current"));
6765         return FALSE;
6766     }
6767     return TRUE;
6768 }
6769
6770 Boolean
6771 OnlyMove (int *x, int *y, Boolean captures)
6772 {
6773     DisambiguateClosure cl;
6774     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6775     switch(gameMode) {
6776       case MachinePlaysBlack:
6777       case IcsPlayingWhite:
6778       case BeginningOfGame:
6779         if(!WhiteOnMove(currentMove)) return FALSE;
6780         break;
6781       case MachinePlaysWhite:
6782       case IcsPlayingBlack:
6783         if(WhiteOnMove(currentMove)) return FALSE;
6784         break;
6785       case EditGame:
6786         break;
6787       default:
6788         return FALSE;
6789     }
6790     cl.pieceIn = EmptySquare;
6791     cl.rfIn = *y;
6792     cl.ffIn = *x;
6793     cl.rtIn = -1;
6794     cl.ftIn = -1;
6795     cl.promoCharIn = NULLCHAR;
6796     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6797     if( cl.kind == NormalMove ||
6798         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6799         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6800         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6801       fromX = cl.ff;
6802       fromY = cl.rf;
6803       *x = cl.ft;
6804       *y = cl.rt;
6805       return TRUE;
6806     }
6807     if(cl.kind != ImpossibleMove) return FALSE;
6808     cl.pieceIn = EmptySquare;
6809     cl.rfIn = -1;
6810     cl.ffIn = -1;
6811     cl.rtIn = *y;
6812     cl.ftIn = *x;
6813     cl.promoCharIn = NULLCHAR;
6814     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6815     if( cl.kind == NormalMove ||
6816         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6817         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6818         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6819       fromX = cl.ff;
6820       fromY = cl.rf;
6821       *x = cl.ft;
6822       *y = cl.rt;
6823       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6824       return TRUE;
6825     }
6826     return FALSE;
6827 }
6828
6829 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6830 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6831 int lastLoadGameUseList = FALSE;
6832 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6833 ChessMove lastLoadGameStart = EndOfFile;
6834 int doubleClick;
6835 Boolean addToBookFlag;
6836
6837 void
6838 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6839 {
6840     ChessMove moveType;
6841     ChessSquare pup;
6842     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6843
6844     /* Check if the user is playing in turn.  This is complicated because we
6845        let the user "pick up" a piece before it is his turn.  So the piece he
6846        tried to pick up may have been captured by the time he puts it down!
6847        Therefore we use the color the user is supposed to be playing in this
6848        test, not the color of the piece that is currently on the starting
6849        square---except in EditGame mode, where the user is playing both
6850        sides; fortunately there the capture race can't happen.  (It can
6851        now happen in IcsExamining mode, but that's just too bad.  The user
6852        will get a somewhat confusing message in that case.)
6853        */
6854
6855     switch (gameMode) {
6856       case AnalyzeFile:
6857       case TwoMachinesPlay:
6858       case EndOfGame:
6859       case IcsObserving:
6860       case IcsIdle:
6861         /* We switched into a game mode where moves are not accepted,
6862            perhaps while the mouse button was down. */
6863         return;
6864
6865       case MachinePlaysWhite:
6866         /* User is moving for Black */
6867         if (WhiteOnMove(currentMove)) {
6868             DisplayMoveError(_("It is White's turn"));
6869             return;
6870         }
6871         break;
6872
6873       case MachinePlaysBlack:
6874         /* User is moving for White */
6875         if (!WhiteOnMove(currentMove)) {
6876             DisplayMoveError(_("It is Black's turn"));
6877             return;
6878         }
6879         break;
6880
6881       case PlayFromGameFile:
6882             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6883       case EditGame:
6884       case IcsExamining:
6885       case BeginningOfGame:
6886       case AnalyzeMode:
6887       case Training:
6888         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6889         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6890             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6891             /* User is moving for Black */
6892             if (WhiteOnMove(currentMove)) {
6893                 DisplayMoveError(_("It is White's turn"));
6894                 return;
6895             }
6896         } else {
6897             /* User is moving for White */
6898             if (!WhiteOnMove(currentMove)) {
6899                 DisplayMoveError(_("It is Black's turn"));
6900                 return;
6901             }
6902         }
6903         break;
6904
6905       case IcsPlayingBlack:
6906         /* User is moving for Black */
6907         if (WhiteOnMove(currentMove)) {
6908             if (!appData.premove) {
6909                 DisplayMoveError(_("It is White's turn"));
6910             } else if (toX >= 0 && toY >= 0) {
6911                 premoveToX = toX;
6912                 premoveToY = toY;
6913                 premoveFromX = fromX;
6914                 premoveFromY = fromY;
6915                 premovePromoChar = promoChar;
6916                 gotPremove = 1;
6917                 if (appData.debugMode)
6918                     fprintf(debugFP, "Got premove: fromX %d,"
6919                             "fromY %d, toX %d, toY %d\n",
6920                             fromX, fromY, toX, toY);
6921             }
6922             return;
6923         }
6924         break;
6925
6926       case IcsPlayingWhite:
6927         /* User is moving for White */
6928         if (!WhiteOnMove(currentMove)) {
6929             if (!appData.premove) {
6930                 DisplayMoveError(_("It is Black's turn"));
6931             } else if (toX >= 0 && toY >= 0) {
6932                 premoveToX = toX;
6933                 premoveToY = toY;
6934                 premoveFromX = fromX;
6935                 premoveFromY = fromY;
6936                 premovePromoChar = promoChar;
6937                 gotPremove = 1;
6938                 if (appData.debugMode)
6939                     fprintf(debugFP, "Got premove: fromX %d,"
6940                             "fromY %d, toX %d, toY %d\n",
6941                             fromX, fromY, toX, toY);
6942             }
6943             return;
6944         }
6945         break;
6946
6947       default:
6948         break;
6949
6950       case EditPosition:
6951         /* EditPosition, empty square, or different color piece;
6952            click-click move is possible */
6953         if (toX == -2 || toY == -2) {
6954             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6955             DrawPosition(FALSE, boards[currentMove]);
6956             return;
6957         } else if (toX >= 0 && toY >= 0) {
6958             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6959                 ChessSquare q, p = boards[0][rf][ff];
6960                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6961                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6962                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6963                 if(PieceToChar(q) == '+') gatingPiece = p;
6964             }
6965             boards[0][toY][toX] = boards[0][fromY][fromX];
6966             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6967                 if(boards[0][fromY][0] != EmptySquare) {
6968                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6969                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6970                 }
6971             } else
6972             if(fromX == BOARD_RGHT+1) {
6973                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6974                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6975                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6976                 }
6977             } else
6978             boards[0][fromY][fromX] = gatingPiece;
6979             DrawPosition(FALSE, boards[currentMove]);
6980             return;
6981         }
6982         return;
6983     }
6984
6985     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6986     pup = boards[currentMove][toY][toX];
6987
6988     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6989     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6990          if( pup != EmptySquare ) return;
6991          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6992            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6993                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6994            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6995            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6996            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6997            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6998          fromY = DROP_RANK;
6999     }
7000
7001     /* [HGM] always test for legality, to get promotion info */
7002     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7003                                          fromY, fromX, toY, toX, promoChar);
7004
7005     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7006
7007     /* [HGM] but possibly ignore an IllegalMove result */
7008     if (appData.testLegality) {
7009         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7010             DisplayMoveError(_("Illegal move"));
7011             return;
7012         }
7013     }
7014
7015     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7016         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7017              ClearPremoveHighlights(); // was included
7018         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7019         return;
7020     }
7021
7022     if(addToBookFlag) { // adding moves to book
7023         char buf[MSG_SIZ], move[MSG_SIZ];
7024         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7025         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7026         AddBookMove(buf);
7027         addToBookFlag = FALSE;
7028         ClearHighlights();
7029         return;
7030     }
7031
7032     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7033 }
7034
7035 /* Common tail of UserMoveEvent and DropMenuEvent */
7036 int
7037 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7038 {
7039     char *bookHit = 0;
7040
7041     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7042         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7043         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7044         if(WhiteOnMove(currentMove)) {
7045             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7046         } else {
7047             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7048         }
7049     }
7050
7051     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7052        move type in caller when we know the move is a legal promotion */
7053     if(moveType == NormalMove && promoChar)
7054         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7055
7056     /* [HGM] <popupFix> The following if has been moved here from
7057        UserMoveEvent(). Because it seemed to belong here (why not allow
7058        piece drops in training games?), and because it can only be
7059        performed after it is known to what we promote. */
7060     if (gameMode == Training) {
7061       /* compare the move played on the board to the next move in the
7062        * game. If they match, display the move and the opponent's response.
7063        * If they don't match, display an error message.
7064        */
7065       int saveAnimate;
7066       Board testBoard;
7067       CopyBoard(testBoard, boards[currentMove]);
7068       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7069
7070       if (CompareBoards(testBoard, boards[currentMove+1])) {
7071         ForwardInner(currentMove+1);
7072
7073         /* Autoplay the opponent's response.
7074          * if appData.animate was TRUE when Training mode was entered,
7075          * the response will be animated.
7076          */
7077         saveAnimate = appData.animate;
7078         appData.animate = animateTraining;
7079         ForwardInner(currentMove+1);
7080         appData.animate = saveAnimate;
7081
7082         /* check for the end of the game */
7083         if (currentMove >= forwardMostMove) {
7084           gameMode = PlayFromGameFile;
7085           ModeHighlight();
7086           SetTrainingModeOff();
7087           DisplayInformation(_("End of game"));
7088         }
7089       } else {
7090         DisplayError(_("Incorrect move"), 0);
7091       }
7092       return 1;
7093     }
7094
7095   /* Ok, now we know that the move is good, so we can kill
7096      the previous line in Analysis Mode */
7097   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7098                                 && currentMove < forwardMostMove) {
7099     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7100     else forwardMostMove = currentMove;
7101   }
7102
7103   ClearMap();
7104
7105   /* If we need the chess program but it's dead, restart it */
7106   ResurrectChessProgram();
7107
7108   /* A user move restarts a paused game*/
7109   if (pausing)
7110     PauseEvent();
7111
7112   thinkOutput[0] = NULLCHAR;
7113
7114   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7115
7116   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7117     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7118     return 1;
7119   }
7120
7121   if (gameMode == BeginningOfGame) {
7122     if (appData.noChessProgram) {
7123       gameMode = EditGame;
7124       SetGameInfo();
7125     } else {
7126       char buf[MSG_SIZ];
7127       gameMode = MachinePlaysBlack;
7128       StartClocks();
7129       SetGameInfo();
7130       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7131       DisplayTitle(buf);
7132       if (first.sendName) {
7133         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7134         SendToProgram(buf, &first);
7135       }
7136       StartClocks();
7137     }
7138     ModeHighlight();
7139   }
7140
7141   /* Relay move to ICS or chess engine */
7142   if (appData.icsActive) {
7143     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7144         gameMode == IcsExamining) {
7145       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7146         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7147         SendToICS("draw ");
7148         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7149       }
7150       // also send plain move, in case ICS does not understand atomic claims
7151       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7152       ics_user_moved = 1;
7153     }
7154   } else {
7155     if (first.sendTime && (gameMode == BeginningOfGame ||
7156                            gameMode == MachinePlaysWhite ||
7157                            gameMode == MachinePlaysBlack)) {
7158       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7159     }
7160     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7161          // [HGM] book: if program might be playing, let it use book
7162         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7163         first.maybeThinking = TRUE;
7164     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7165         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7166         SendBoard(&first, currentMove+1);
7167         if(second.analyzing) {
7168             if(!second.useSetboard) SendToProgram("undo\n", &second);
7169             SendBoard(&second, currentMove+1);
7170         }
7171     } else {
7172         SendMoveToProgram(forwardMostMove-1, &first);
7173         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7174     }
7175     if (currentMove == cmailOldMove + 1) {
7176       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7177     }
7178   }
7179
7180   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7181
7182   switch (gameMode) {
7183   case EditGame:
7184     if(appData.testLegality)
7185     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7186     case MT_NONE:
7187     case MT_CHECK:
7188       break;
7189     case MT_CHECKMATE:
7190     case MT_STAINMATE:
7191       if (WhiteOnMove(currentMove)) {
7192         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7193       } else {
7194         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7195       }
7196       break;
7197     case MT_STALEMATE:
7198       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7199       break;
7200     }
7201     break;
7202
7203   case MachinePlaysBlack:
7204   case MachinePlaysWhite:
7205     /* disable certain menu options while machine is thinking */
7206     SetMachineThinkingEnables();
7207     break;
7208
7209   default:
7210     break;
7211   }
7212
7213   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7214   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7215
7216   if(bookHit) { // [HGM] book: simulate book reply
7217         static char bookMove[MSG_SIZ]; // a bit generous?
7218
7219         programStats.nodes = programStats.depth = programStats.time =
7220         programStats.score = programStats.got_only_move = 0;
7221         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7222
7223         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7224         strcat(bookMove, bookHit);
7225         HandleMachineMove(bookMove, &first);
7226   }
7227   return 1;
7228 }
7229
7230 void
7231 MarkByFEN(char *fen)
7232 {
7233         int r, f;
7234         if(!appData.markers || !appData.highlightDragging) return;
7235         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7236         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7237         while(*fen) {
7238             int s = 0;
7239             marker[r][f] = 0;
7240             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7241             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7242             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7243             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7244             if(*fen == 'T') marker[r][f++] = 0; else
7245             if(*fen == 'Y') marker[r][f++] = 1; else
7246             if(*fen == 'G') marker[r][f++] = 3; else
7247             if(*fen == 'B') marker[r][f++] = 4; else
7248             if(*fen == 'C') marker[r][f++] = 5; else
7249             if(*fen == 'M') marker[r][f++] = 6; else
7250             if(*fen == 'W') marker[r][f++] = 7; else
7251             if(*fen == 'D') marker[r][f++] = 8; else
7252             if(*fen == 'R') marker[r][f++] = 2; else {
7253                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7254               f += s; fen -= s>0;
7255             }
7256             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7257             if(r < 0) break;
7258             fen++;
7259         }
7260         DrawPosition(TRUE, NULL);
7261 }
7262
7263 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7264
7265 void
7266 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7267 {
7268     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7269     Markers *m = (Markers *) closure;
7270     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7271         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7272                          || kind == WhiteCapturesEnPassant
7273                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7274     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7275 }
7276
7277 static int hoverSavedValid;
7278
7279 void
7280 MarkTargetSquares (int clear)
7281 {
7282   int x, y, sum=0;
7283   if(clear) { // no reason to ever suppress clearing
7284     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7285     hoverSavedValid = 0;
7286     if(!sum) return; // nothing was cleared,no redraw needed
7287   } else {
7288     int capt = 0;
7289     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7290        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7291     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7292     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7293       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7294       if(capt)
7295       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7296     }
7297   }
7298   DrawPosition(FALSE, NULL);
7299 }
7300
7301 int
7302 Explode (Board board, int fromX, int fromY, int toX, int toY)
7303 {
7304     if(gameInfo.variant == VariantAtomic &&
7305        (board[toY][toX] != EmptySquare ||                     // capture?
7306         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7307                          board[fromY][fromX] == BlackPawn   )
7308       )) {
7309         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7310         return TRUE;
7311     }
7312     return FALSE;
7313 }
7314
7315 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7316
7317 int
7318 CanPromote (ChessSquare piece, int y)
7319 {
7320         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7321         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7322         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7323         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7324            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7325            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7326          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7327         return (piece == BlackPawn && y <= zone ||
7328                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7329                 piece == BlackLance && y <= zone ||
7330                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7331 }
7332
7333 void
7334 HoverEvent (int xPix, int yPix, int x, int y)
7335 {
7336         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7337         int r, f;
7338         if(!first.highlight) return;
7339         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7340         if(x == oldX && y == oldY) return; // only do something if we enter new square
7341         oldFromX = fromX; oldFromY = fromY;
7342         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7343           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7344             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7345           hoverSavedValid = 1;
7346         } else if(oldX != x || oldY != y) {
7347           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7348           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7349           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7350             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7351           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7352             char buf[MSG_SIZ];
7353             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7354             SendToProgram(buf, &first);
7355           }
7356           oldX = x; oldY = y;
7357 //        SetHighlights(fromX, fromY, x, y);
7358         }
7359 }
7360
7361 void ReportClick(char *action, int x, int y)
7362 {
7363         char buf[MSG_SIZ]; // Inform engine of what user does
7364         int r, f;
7365         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7366           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7367             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7368         if(!first.highlight || gameMode == EditPosition) return;
7369         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7370         SendToProgram(buf, &first);
7371 }
7372
7373 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7374
7375 void
7376 LeftClick (ClickType clickType, int xPix, int yPix)
7377 {
7378     int x, y;
7379     Boolean saveAnimate;
7380     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7381     char promoChoice = NULLCHAR;
7382     ChessSquare piece;
7383     static TimeMark lastClickTime, prevClickTime;
7384
7385     x = EventToSquare(xPix, BOARD_WIDTH);
7386     y = EventToSquare(yPix, BOARD_HEIGHT);
7387     if (!flipView && y >= 0) {
7388         y = BOARD_HEIGHT - 1 - y;
7389     }
7390     if (flipView && x >= 0) {
7391         x = BOARD_WIDTH - 1 - x;
7392     }
7393
7394     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7395         static int dummy;
7396         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7397         right = TRUE;
7398         return;
7399     }
7400
7401     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7402
7403     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7404
7405     if (clickType == Press) ErrorPopDown();
7406     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7407
7408     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7409         defaultPromoChoice = promoSweep;
7410         promoSweep = EmptySquare;   // terminate sweep
7411         promoDefaultAltered = TRUE;
7412         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7413     }
7414
7415     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7416         if(clickType == Release) return; // ignore upclick of click-click destination
7417         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7418         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7419         if(gameInfo.holdingsWidth &&
7420                 (WhiteOnMove(currentMove)
7421                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7422                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7423             // click in right holdings, for determining promotion piece
7424             ChessSquare p = boards[currentMove][y][x];
7425             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7426             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7427             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7428                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7429                 fromX = fromY = -1;
7430                 return;
7431             }
7432         }
7433         DrawPosition(FALSE, boards[currentMove]);
7434         return;
7435     }
7436
7437     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7438     if(clickType == Press
7439             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7440               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7441               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7442         return;
7443
7444     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7445         // could be static click on premove from-square: abort premove
7446         gotPremove = 0;
7447         ClearPremoveHighlights();
7448     }
7449
7450     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7451         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7452
7453     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7454         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7455                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7456         defaultPromoChoice = DefaultPromoChoice(side);
7457     }
7458
7459     autoQueen = appData.alwaysPromoteToQueen;
7460
7461     if (fromX == -1) {
7462       int originalY = y;
7463       gatingPiece = EmptySquare;
7464       if (clickType != Press) {
7465         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7466             DragPieceEnd(xPix, yPix); dragging = 0;
7467             DrawPosition(FALSE, NULL);
7468         }
7469         return;
7470       }
7471       doubleClick = FALSE;
7472       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7473         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7474       }
7475       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7476       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7477          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7478          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7479             /* First square */
7480             if (OKToStartUserMove(fromX, fromY)) {
7481                 second = 0;
7482                 ReportClick("lift", x, y);
7483                 MarkTargetSquares(0);
7484                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7485                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7486                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7487                     promoSweep = defaultPromoChoice;
7488                     selectFlag = 0; lastX = xPix; lastY = yPix;
7489                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7490                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7491                 }
7492                 if (appData.highlightDragging) {
7493                     SetHighlights(fromX, fromY, -1, -1);
7494                 } else {
7495                     ClearHighlights();
7496                 }
7497             } else fromX = fromY = -1;
7498             return;
7499         }
7500     }
7501 printf("to click %d,%d\n",x,y);
7502     /* fromX != -1 */
7503     if (clickType == Press && gameMode != EditPosition) {
7504         ChessSquare fromP;
7505         ChessSquare toP;
7506         int frc;
7507
7508         // ignore off-board to clicks
7509         if(y < 0 || x < 0) return;
7510
7511         /* Check if clicking again on the same color piece */
7512         fromP = boards[currentMove][fromY][fromX];
7513         toP = boards[currentMove][y][x];
7514         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7515         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7516             legal[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7517            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7518              WhitePawn <= toP && toP <= WhiteKing &&
7519              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7520              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7521             (BlackPawn <= fromP && fromP <= BlackKing &&
7522              BlackPawn <= toP && toP <= BlackKing &&
7523              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7524              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7525             /* Clicked again on same color piece -- changed his mind */
7526             second = (x == fromX && y == fromY);
7527             killX = killY = -1;
7528             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7529                 second = FALSE; // first double-click rather than scond click
7530                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7531             }
7532             promoDefaultAltered = FALSE;
7533             MarkTargetSquares(1);
7534            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7535             if (appData.highlightDragging) {
7536                 SetHighlights(x, y, -1, -1);
7537             } else {
7538                 ClearHighlights();
7539             }
7540             if (OKToStartUserMove(x, y)) {
7541                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7542                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7543                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7544                  gatingPiece = boards[currentMove][fromY][fromX];
7545                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7546                 fromX = x;
7547                 fromY = y; dragging = 1;
7548                 ReportClick("lift", x, y);
7549                 MarkTargetSquares(0);
7550                 DragPieceBegin(xPix, yPix, FALSE);
7551                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7552                     promoSweep = defaultPromoChoice;
7553                     selectFlag = 0; lastX = xPix; lastY = yPix;
7554                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7555                 }
7556             }
7557            }
7558            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7559            second = FALSE;
7560         }
7561         // ignore clicks on holdings
7562         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7563     }
7564 printf("A type=%d\n",clickType);
7565
7566     if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7567         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7568         return;
7569     }
7570
7571     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7572         DragPieceEnd(xPix, yPix); dragging = 0;
7573         if(clearFlag) {
7574             // a deferred attempt to click-click move an empty square on top of a piece
7575             boards[currentMove][y][x] = EmptySquare;
7576             ClearHighlights();
7577             DrawPosition(FALSE, boards[currentMove]);
7578             fromX = fromY = -1; clearFlag = 0;
7579             return;
7580         }
7581         if (appData.animateDragging) {
7582             /* Undo animation damage if any */
7583             DrawPosition(FALSE, NULL);
7584         }
7585         if (second || sweepSelecting) {
7586             /* Second up/down in same square; just abort move */
7587             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7588             second = sweepSelecting = 0;
7589             fromX = fromY = -1;
7590             gatingPiece = EmptySquare;
7591             MarkTargetSquares(1);
7592             ClearHighlights();
7593             gotPremove = 0;
7594             ClearPremoveHighlights();
7595         } else {
7596             /* First upclick in same square; start click-click mode */
7597             SetHighlights(x, y, -1, -1);
7598         }
7599         return;
7600     }
7601
7602     clearFlag = 0;
7603 printf("B\n");
7604     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7605        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7606         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7607         DisplayMessage(_("only marked squares are legal"),"");
7608         DrawPosition(TRUE, NULL);
7609         return; // ignore to-click
7610     }
7611 printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
7612     /* we now have a different from- and (possibly off-board) to-square */
7613     /* Completed move */
7614     if(!sweepSelecting) {
7615         toX = x;
7616         toY = y;
7617     }
7618
7619     piece = boards[currentMove][fromY][fromX];
7620
7621     saveAnimate = appData.animate;
7622     if (clickType == Press) {
7623         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7624         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7625             // must be Edit Position mode with empty-square selected
7626             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7627             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7628             return;
7629         }
7630         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7631             return;
7632         }
7633         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7634             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7635         } else
7636         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7637         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7638           if(appData.sweepSelect) {
7639             promoSweep = defaultPromoChoice;
7640             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7641             selectFlag = 0; lastX = xPix; lastY = yPix;
7642             Sweep(0); // Pawn that is going to promote: preview promotion piece
7643             sweepSelecting = 1;
7644             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7645             MarkTargetSquares(1);
7646           }
7647           return; // promo popup appears on up-click
7648         }
7649         /* Finish clickclick move */
7650         if (appData.animate || appData.highlightLastMove) {
7651             SetHighlights(fromX, fromY, toX, toY);
7652         } else {
7653             ClearHighlights();
7654         }
7655     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7656         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7657         if (appData.animate || appData.highlightLastMove) {
7658             SetHighlights(fromX, fromY, toX, toY);
7659         } else {
7660             ClearHighlights();
7661         }
7662     } else {
7663 #if 0
7664 // [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
7665         /* Finish drag move */
7666         if (appData.highlightLastMove) {
7667             SetHighlights(fromX, fromY, toX, toY);
7668         } else {
7669             ClearHighlights();
7670         }
7671 #endif
7672         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7673         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7674           dragging *= 2;            // flag button-less dragging if we are dragging
7675           MarkTargetSquares(1);
7676           if(x == killX && y == killY) killX = killY = -1; else {
7677             killX = x; killY = y;     //remeber this square as intermediate
7678             ReportClick("put", x, y); // and inform engine
7679             ReportClick("lift", x, y);
7680             MarkTargetSquares(0);
7681             return;
7682           }
7683         }
7684         DragPieceEnd(xPix, yPix); dragging = 0;
7685         /* Don't animate move and drag both */
7686         appData.animate = FALSE;
7687     }
7688
7689     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7690     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7691         ChessSquare piece = boards[currentMove][fromY][fromX];
7692         if(gameMode == EditPosition && piece != EmptySquare &&
7693            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7694             int n;
7695
7696             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7697                 n = PieceToNumber(piece - (int)BlackPawn);
7698                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7699                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7700                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7701             } else
7702             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7703                 n = PieceToNumber(piece);
7704                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7705                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7706                 boards[currentMove][n][BOARD_WIDTH-2]++;
7707             }
7708             boards[currentMove][fromY][fromX] = EmptySquare;
7709         }
7710         ClearHighlights();
7711         fromX = fromY = -1;
7712         MarkTargetSquares(1);
7713         DrawPosition(TRUE, boards[currentMove]);
7714         return;
7715     }
7716
7717     // off-board moves should not be highlighted
7718     if(x < 0 || y < 0) ClearHighlights();
7719     else ReportClick("put", x, y);
7720
7721     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7722
7723     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7724         SetHighlights(fromX, fromY, toX, toY);
7725         MarkTargetSquares(1);
7726         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7727             // [HGM] super: promotion to captured piece selected from holdings
7728             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7729             promotionChoice = TRUE;
7730             // kludge follows to temporarily execute move on display, without promoting yet
7731             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7732             boards[currentMove][toY][toX] = p;
7733             DrawPosition(FALSE, boards[currentMove]);
7734             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7735             boards[currentMove][toY][toX] = q;
7736             DisplayMessage("Click in holdings to choose piece", "");
7737             return;
7738         }
7739         PromotionPopUp(promoChoice);
7740     } else {
7741         int oldMove = currentMove;
7742         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7743         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7744         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7745         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7746            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7747             DrawPosition(TRUE, boards[currentMove]);
7748         MarkTargetSquares(1);
7749         fromX = fromY = -1;
7750     }
7751     appData.animate = saveAnimate;
7752     if (appData.animate || appData.animateDragging) {
7753         /* Undo animation damage if needed */
7754         DrawPosition(FALSE, NULL);
7755     }
7756 }
7757
7758 int
7759 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7760 {   // front-end-free part taken out of PieceMenuPopup
7761     int whichMenu; int xSqr, ySqr;
7762
7763     if(seekGraphUp) { // [HGM] seekgraph
7764         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7765         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7766         return -2;
7767     }
7768
7769     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7770          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7771         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7772         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7773         if(action == Press)   {
7774             originalFlip = flipView;
7775             flipView = !flipView; // temporarily flip board to see game from partners perspective
7776             DrawPosition(TRUE, partnerBoard);
7777             DisplayMessage(partnerStatus, "");
7778             partnerUp = TRUE;
7779         } else if(action == Release) {
7780             flipView = originalFlip;
7781             DrawPosition(TRUE, boards[currentMove]);
7782             partnerUp = FALSE;
7783         }
7784         return -2;
7785     }
7786
7787     xSqr = EventToSquare(x, BOARD_WIDTH);
7788     ySqr = EventToSquare(y, BOARD_HEIGHT);
7789     if (action == Release) {
7790         if(pieceSweep != EmptySquare) {
7791             EditPositionMenuEvent(pieceSweep, toX, toY);
7792             pieceSweep = EmptySquare;
7793         } else UnLoadPV(); // [HGM] pv
7794     }
7795     if (action != Press) return -2; // return code to be ignored
7796     switch (gameMode) {
7797       case IcsExamining:
7798         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7799       case EditPosition:
7800         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7801         if (xSqr < 0 || ySqr < 0) return -1;
7802         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7803         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7804         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7805         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7806         NextPiece(0);
7807         return 2; // grab
7808       case IcsObserving:
7809         if(!appData.icsEngineAnalyze) return -1;
7810       case IcsPlayingWhite:
7811       case IcsPlayingBlack:
7812         if(!appData.zippyPlay) goto noZip;
7813       case AnalyzeMode:
7814       case AnalyzeFile:
7815       case MachinePlaysWhite:
7816       case MachinePlaysBlack:
7817       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7818         if (!appData.dropMenu) {
7819           LoadPV(x, y);
7820           return 2; // flag front-end to grab mouse events
7821         }
7822         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7823            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7824       case EditGame:
7825       noZip:
7826         if (xSqr < 0 || ySqr < 0) return -1;
7827         if (!appData.dropMenu || appData.testLegality &&
7828             gameInfo.variant != VariantBughouse &&
7829             gameInfo.variant != VariantCrazyhouse) return -1;
7830         whichMenu = 1; // drop menu
7831         break;
7832       default:
7833         return -1;
7834     }
7835
7836     if (((*fromX = xSqr) < 0) ||
7837         ((*fromY = ySqr) < 0)) {
7838         *fromX = *fromY = -1;
7839         return -1;
7840     }
7841     if (flipView)
7842       *fromX = BOARD_WIDTH - 1 - *fromX;
7843     else
7844       *fromY = BOARD_HEIGHT - 1 - *fromY;
7845
7846     return whichMenu;
7847 }
7848
7849 void
7850 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7851 {
7852 //    char * hint = lastHint;
7853     FrontEndProgramStats stats;
7854
7855     stats.which = cps == &first ? 0 : 1;
7856     stats.depth = cpstats->depth;
7857     stats.nodes = cpstats->nodes;
7858     stats.score = cpstats->score;
7859     stats.time = cpstats->time;
7860     stats.pv = cpstats->movelist;
7861     stats.hint = lastHint;
7862     stats.an_move_index = 0;
7863     stats.an_move_count = 0;
7864
7865     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7866         stats.hint = cpstats->move_name;
7867         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7868         stats.an_move_count = cpstats->nr_moves;
7869     }
7870
7871     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
7872
7873     SetProgramStats( &stats );
7874 }
7875
7876 void
7877 ClearEngineOutputPane (int which)
7878 {
7879     static FrontEndProgramStats dummyStats;
7880     dummyStats.which = which;
7881     dummyStats.pv = "#";
7882     SetProgramStats( &dummyStats );
7883 }
7884
7885 #define MAXPLAYERS 500
7886
7887 char *
7888 TourneyStandings (int display)
7889 {
7890     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7891     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7892     char result, *p, *names[MAXPLAYERS];
7893
7894     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7895         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7896     names[0] = p = strdup(appData.participants);
7897     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7898
7899     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7900
7901     while(result = appData.results[nr]) {
7902         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7903         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7904         wScore = bScore = 0;
7905         switch(result) {
7906           case '+': wScore = 2; break;
7907           case '-': bScore = 2; break;
7908           case '=': wScore = bScore = 1; break;
7909           case ' ':
7910           case '*': return strdup("busy"); // tourney not finished
7911         }
7912         score[w] += wScore;
7913         score[b] += bScore;
7914         games[w]++;
7915         games[b]++;
7916         nr++;
7917     }
7918     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7919     for(w=0; w<nPlayers; w++) {
7920         bScore = -1;
7921         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7922         ranking[w] = b; points[w] = bScore; score[b] = -2;
7923     }
7924     p = malloc(nPlayers*34+1);
7925     for(w=0; w<nPlayers && w<display; w++)
7926         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7927     free(names[0]);
7928     return p;
7929 }
7930
7931 void
7932 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7933 {       // count all piece types
7934         int p, f, r;
7935         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7936         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7937         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7938                 p = board[r][f];
7939                 pCnt[p]++;
7940                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7941                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7942                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7943                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7944                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7945                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7946         }
7947 }
7948
7949 int
7950 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7951 {
7952         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7953         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7954
7955         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7956         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7957         if(myPawns == 2 && nMine == 3) // KPP
7958             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7959         if(myPawns == 1 && nMine == 2) // KP
7960             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7961         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7962             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7963         if(myPawns) return FALSE;
7964         if(pCnt[WhiteRook+side])
7965             return pCnt[BlackRook-side] ||
7966                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7967                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7968                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7969         if(pCnt[WhiteCannon+side]) {
7970             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7971             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7972         }
7973         if(pCnt[WhiteKnight+side])
7974             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7975         return FALSE;
7976 }
7977
7978 int
7979 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7980 {
7981         VariantClass v = gameInfo.variant;
7982
7983         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7984         if(v == VariantShatranj) return TRUE; // always winnable through baring
7985         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7986         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7987
7988         if(v == VariantXiangqi) {
7989                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7990
7991                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7992                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7993                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7994                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7995                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7996                 if(stale) // we have at least one last-rank P plus perhaps C
7997                     return majors // KPKX
7998                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7999                 else // KCA*E*
8000                     return pCnt[WhiteFerz+side] // KCAK
8001                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8002                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8003                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8004
8005         } else if(v == VariantKnightmate) {
8006                 if(nMine == 1) return FALSE;
8007                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8008         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8009                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8010
8011                 if(nMine == 1) return FALSE; // bare King
8012                 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
8013                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8014                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8015                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8016                 if(pCnt[WhiteKnight+side])
8017                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8018                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8019                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8020                 if(nBishops)
8021                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8022                 if(pCnt[WhiteAlfil+side])
8023                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8024                 if(pCnt[WhiteWazir+side])
8025                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8026         }
8027
8028         return TRUE;
8029 }
8030
8031 int
8032 CompareWithRights (Board b1, Board b2)
8033 {
8034     int rights = 0;
8035     if(!CompareBoards(b1, b2)) return FALSE;
8036     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8037     /* compare castling rights */
8038     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8039            rights++; /* King lost rights, while rook still had them */
8040     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8041         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8042            rights++; /* but at least one rook lost them */
8043     }
8044     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8045            rights++;
8046     if( b1[CASTLING][5] != NoRights ) {
8047         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8048            rights++;
8049     }
8050     return rights == 0;
8051 }
8052
8053 int
8054 Adjudicate (ChessProgramState *cps)
8055 {       // [HGM] some adjudications useful with buggy engines
8056         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8057         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8058         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8059         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8060         int k, drop, count = 0; static int bare = 1;
8061         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8062         Boolean canAdjudicate = !appData.icsActive;
8063
8064         // most tests only when we understand the game, i.e. legality-checking on
8065             if( appData.testLegality )
8066             {   /* [HGM] Some more adjudications for obstinate engines */
8067                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8068                 static int moveCount = 6;
8069                 ChessMove result;
8070                 char *reason = NULL;
8071
8072                 /* Count what is on board. */
8073                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8074
8075                 /* Some material-based adjudications that have to be made before stalemate test */
8076                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8077                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8078                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8079                      if(canAdjudicate && appData.checkMates) {
8080                          if(engineOpponent)
8081                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8082                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8083                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8084                          return 1;
8085                      }
8086                 }
8087
8088                 /* Bare King in Shatranj (loses) or Losers (wins) */
8089                 if( nrW == 1 || nrB == 1) {
8090                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8091                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8092                      if(canAdjudicate && appData.checkMates) {
8093                          if(engineOpponent)
8094                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8095                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8096                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8097                          return 1;
8098                      }
8099                   } else
8100                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8101                   {    /* bare King */
8102                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8103                         if(canAdjudicate && appData.checkMates) {
8104                             /* but only adjudicate if adjudication enabled */
8105                             if(engineOpponent)
8106                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8107                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8108                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8109                             return 1;
8110                         }
8111                   }
8112                 } else bare = 1;
8113
8114
8115             // don't wait for engine to announce game end if we can judge ourselves
8116             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8117               case MT_CHECK:
8118                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8119                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8120                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8121                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8122                             checkCnt++;
8123                         if(checkCnt >= 2) {
8124                             reason = "Xboard adjudication: 3rd check";
8125                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8126                             break;
8127                         }
8128                     }
8129                 }
8130               case MT_NONE:
8131               default:
8132                 break;
8133               case MT_STEALMATE:
8134               case MT_STALEMATE:
8135               case MT_STAINMATE:
8136                 reason = "Xboard adjudication: Stalemate";
8137                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8138                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8139                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8140                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8141                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8142                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8143                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8144                                                                         EP_CHECKMATE : EP_WINS);
8145                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8146                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8147                 }
8148                 break;
8149               case MT_CHECKMATE:
8150                 reason = "Xboard adjudication: Checkmate";
8151                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8152                 if(gameInfo.variant == VariantShogi) {
8153                     if(forwardMostMove > backwardMostMove
8154                        && moveList[forwardMostMove-1][1] == '@'
8155                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8156                         reason = "XBoard adjudication: pawn-drop mate";
8157                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8158                     }
8159                 }
8160                 break;
8161             }
8162
8163                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8164                     case EP_STALEMATE:
8165                         result = GameIsDrawn; break;
8166                     case EP_CHECKMATE:
8167                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8168                     case EP_WINS:
8169                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8170                     default:
8171                         result = EndOfFile;
8172                 }
8173                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8174                     if(engineOpponent)
8175                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8176                     GameEnds( result, reason, GE_XBOARD );
8177                     return 1;
8178                 }
8179
8180                 /* Next absolutely insufficient mating material. */
8181                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8182                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8183                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8184
8185                      /* always flag draws, for judging claims */
8186                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8187
8188                      if(canAdjudicate && appData.materialDraws) {
8189                          /* but only adjudicate them if adjudication enabled */
8190                          if(engineOpponent) {
8191                            SendToProgram("force\n", engineOpponent); // suppress reply
8192                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8193                          }
8194                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8195                          return 1;
8196                      }
8197                 }
8198
8199                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8200                 if(gameInfo.variant == VariantXiangqi ?
8201                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8202                  : nrW + nrB == 4 &&
8203                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8204                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8205                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8206                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8207                    ) ) {
8208                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8209                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8210                           if(engineOpponent) {
8211                             SendToProgram("force\n", engineOpponent); // suppress reply
8212                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8213                           }
8214                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8215                           return 1;
8216                      }
8217                 } else moveCount = 6;
8218             }
8219
8220         // Repetition draws and 50-move rule can be applied independently of legality testing
8221
8222                 /* Check for rep-draws */
8223                 count = 0;
8224                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8225                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8226                 for(k = forwardMostMove-2;
8227                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8228                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8229                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8230                     k-=2)
8231                 {   int rights=0;
8232                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8233                         /* compare castling rights */
8234                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8235                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8236                                 rights++; /* King lost rights, while rook still had them */
8237                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8238                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8239                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8240                                    rights++; /* but at least one rook lost them */
8241                         }
8242                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8243                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8244                                 rights++;
8245                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8246                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8247                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8248                                    rights++;
8249                         }
8250                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8251                             && appData.drawRepeats > 1) {
8252                              /* adjudicate after user-specified nr of repeats */
8253                              int result = GameIsDrawn;
8254                              char *details = "XBoard adjudication: repetition draw";
8255                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8256                                 // [HGM] xiangqi: check for forbidden perpetuals
8257                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8258                                 for(m=forwardMostMove; m>k; m-=2) {
8259                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8260                                         ourPerpetual = 0; // the current mover did not always check
8261                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8262                                         hisPerpetual = 0; // the opponent did not always check
8263                                 }
8264                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8265                                                                         ourPerpetual, hisPerpetual);
8266                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8267                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8268                                     details = "Xboard adjudication: perpetual checking";
8269                                 } else
8270                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8271                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8272                                 } else
8273                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8274                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8275                                         result = BlackWins;
8276                                         details = "Xboard adjudication: repetition";
8277                                     }
8278                                 } else // it must be XQ
8279                                 // Now check for perpetual chases
8280                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8281                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8282                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8283                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8284                                         static char resdet[MSG_SIZ];
8285                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8286                                         details = resdet;
8287                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8288                                     } else
8289                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8290                                         break; // Abort repetition-checking loop.
8291                                 }
8292                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8293                              }
8294                              if(engineOpponent) {
8295                                SendToProgram("force\n", engineOpponent); // suppress reply
8296                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8297                              }
8298                              GameEnds( result, details, GE_XBOARD );
8299                              return 1;
8300                         }
8301                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8302                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8303                     }
8304                 }
8305
8306                 /* Now we test for 50-move draws. Determine ply count */
8307                 count = forwardMostMove;
8308                 /* look for last irreversble move */
8309                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8310                     count--;
8311                 /* if we hit starting position, add initial plies */
8312                 if( count == backwardMostMove )
8313                     count -= initialRulePlies;
8314                 count = forwardMostMove - count;
8315                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8316                         // adjust reversible move counter for checks in Xiangqi
8317                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8318                         if(i < backwardMostMove) i = backwardMostMove;
8319                         while(i <= forwardMostMove) {
8320                                 lastCheck = inCheck; // check evasion does not count
8321                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8322                                 if(inCheck || lastCheck) count--; // check does not count
8323                                 i++;
8324                         }
8325                 }
8326                 if( count >= 100)
8327                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8328                          /* this is used to judge if draw claims are legal */
8329                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8330                          if(engineOpponent) {
8331                            SendToProgram("force\n", engineOpponent); // suppress reply
8332                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8333                          }
8334                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8335                          return 1;
8336                 }
8337
8338                 /* if draw offer is pending, treat it as a draw claim
8339                  * when draw condition present, to allow engines a way to
8340                  * claim draws before making their move to avoid a race
8341                  * condition occurring after their move
8342                  */
8343                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8344                          char *p = NULL;
8345                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8346                              p = "Draw claim: 50-move rule";
8347                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8348                              p = "Draw claim: 3-fold repetition";
8349                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8350                              p = "Draw claim: insufficient mating material";
8351                          if( p != NULL && canAdjudicate) {
8352                              if(engineOpponent) {
8353                                SendToProgram("force\n", engineOpponent); // suppress reply
8354                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8355                              }
8356                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8357                              return 1;
8358                          }
8359                 }
8360
8361                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8362                     if(engineOpponent) {
8363                       SendToProgram("force\n", engineOpponent); // suppress reply
8364                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8365                     }
8366                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8367                     return 1;
8368                 }
8369         return 0;
8370 }
8371
8372 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8373 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8374 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8375
8376 static int
8377 BitbaseProbe ()
8378 {
8379     int pieces[10], squares[10], cnt=0, r, f, res;
8380     static int loaded;
8381     static PPROBE_EGBB probeBB;
8382     if(!appData.testLegality) return 10;
8383     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8384     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8385     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8386     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8387         ChessSquare piece = boards[forwardMostMove][r][f];
8388         int black = (piece >= BlackPawn);
8389         int type = piece - black*BlackPawn;
8390         if(piece == EmptySquare) continue;
8391         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8392         if(type == WhiteKing) type = WhiteQueen + 1;
8393         type = egbbCode[type];
8394         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8395         pieces[cnt] = type + black*6;
8396         if(++cnt > 5) return 11;
8397     }
8398     pieces[cnt] = squares[cnt] = 0;
8399     // probe EGBB
8400     if(loaded == 2) return 13; // loading failed before
8401     if(loaded == 0) {
8402         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8403         HMODULE lib;
8404         PLOAD_EGBB loadBB;
8405         loaded = 2; // prepare for failure
8406         if(!path) return 13; // no egbb installed
8407         strncpy(buf, path + 8, MSG_SIZ);
8408         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8409         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8410         lib = LoadLibrary(buf);
8411         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8412         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8413         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8414         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8415         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8416         loaded = 1; // success!
8417     }
8418     res = probeBB(forwardMostMove & 1, pieces, squares);
8419     return res > 0 ? 1 : res < 0 ? -1 : 0;
8420 }
8421
8422 char *
8423 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8424 {   // [HGM] book: this routine intercepts moves to simulate book replies
8425     char *bookHit = NULL;
8426
8427     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8428         char buf[MSG_SIZ];
8429         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8430         SendToProgram(buf, cps);
8431     }
8432     //first determine if the incoming move brings opponent into his book
8433     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8434         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8435     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8436     if(bookHit != NULL && !cps->bookSuspend) {
8437         // make sure opponent is not going to reply after receiving move to book position
8438         SendToProgram("force\n", cps);
8439         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8440     }
8441     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8442     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8443     // now arrange restart after book miss
8444     if(bookHit) {
8445         // after a book hit we never send 'go', and the code after the call to this routine
8446         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8447         char buf[MSG_SIZ], *move = bookHit;
8448         if(cps->useSAN) {
8449             int fromX, fromY, toX, toY;
8450             char promoChar;
8451             ChessMove moveType;
8452             move = buf + 30;
8453             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8454                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8455                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8456                                     PosFlags(forwardMostMove),
8457                                     fromY, fromX, toY, toX, promoChar, move);
8458             } else {
8459                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8460                 bookHit = NULL;
8461             }
8462         }
8463         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8464         SendToProgram(buf, cps);
8465         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8466     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8467         SendToProgram("go\n", cps);
8468         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8469     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8470         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8471             SendToProgram("go\n", cps);
8472         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8473     }
8474     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8475 }
8476
8477 int
8478 LoadError (char *errmess, ChessProgramState *cps)
8479 {   // unloads engine and switches back to -ncp mode if it was first
8480     if(cps->initDone) return FALSE;
8481     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8482     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8483     cps->pr = NoProc;
8484     if(cps == &first) {
8485         appData.noChessProgram = TRUE;
8486         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8487         gameMode = BeginningOfGame; ModeHighlight();
8488         SetNCPMode();
8489     }
8490     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8491     DisplayMessage("", ""); // erase waiting message
8492     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8493     return TRUE;
8494 }
8495
8496 char *savedMessage;
8497 ChessProgramState *savedState;
8498 void
8499 DeferredBookMove (void)
8500 {
8501         if(savedState->lastPing != savedState->lastPong)
8502                     ScheduleDelayedEvent(DeferredBookMove, 10);
8503         else
8504         HandleMachineMove(savedMessage, savedState);
8505 }
8506
8507 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8508 static ChessProgramState *stalledEngine;
8509 static char stashedInputMove[MSG_SIZ];
8510
8511 void
8512 HandleMachineMove (char *message, ChessProgramState *cps)
8513 {
8514     static char firstLeg[20];
8515     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8516     char realname[MSG_SIZ];
8517     int fromX, fromY, toX, toY;
8518     ChessMove moveType;
8519     char promoChar, roar;
8520     char *p, *pv=buf1;
8521     int machineWhite, oldError;
8522     char *bookHit;
8523
8524     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8525         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8526         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8527             DisplayError(_("Invalid pairing from pairing engine"), 0);
8528             return;
8529         }
8530         pairingReceived = 1;
8531         NextMatchGame();
8532         return; // Skim the pairing messages here.
8533     }
8534
8535     oldError = cps->userError; cps->userError = 0;
8536
8537 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8538     /*
8539      * Kludge to ignore BEL characters
8540      */
8541     while (*message == '\007') message++;
8542
8543     /*
8544      * [HGM] engine debug message: ignore lines starting with '#' character
8545      */
8546     if(cps->debug && *message == '#') return;
8547
8548     /*
8549      * Look for book output
8550      */
8551     if (cps == &first && bookRequested) {
8552         if (message[0] == '\t' || message[0] == ' ') {
8553             /* Part of the book output is here; append it */
8554             strcat(bookOutput, message);
8555             strcat(bookOutput, "  \n");
8556             return;
8557         } else if (bookOutput[0] != NULLCHAR) {
8558             /* All of book output has arrived; display it */
8559             char *p = bookOutput;
8560             while (*p != NULLCHAR) {
8561                 if (*p == '\t') *p = ' ';
8562                 p++;
8563             }
8564             DisplayInformation(bookOutput);
8565             bookRequested = FALSE;
8566             /* Fall through to parse the current output */
8567         }
8568     }
8569
8570     /*
8571      * Look for machine move.
8572      */
8573     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8574         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8575     {
8576         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8577             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8578             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8579             stalledEngine = cps;
8580             if(appData.ponderNextMove) { // bring opponent out of ponder
8581                 if(gameMode == TwoMachinesPlay) {
8582                     if(cps->other->pause)
8583                         PauseEngine(cps->other);
8584                     else
8585                         SendToProgram("easy\n", cps->other);
8586                 }
8587             }
8588             StopClocks();
8589             return;
8590         }
8591
8592         /* This method is only useful on engines that support ping */
8593         if (cps->lastPing != cps->lastPong) {
8594           if (gameMode == BeginningOfGame) {
8595             /* Extra move from before last new; ignore */
8596             if (appData.debugMode) {
8597                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8598             }
8599           } else {
8600             if (appData.debugMode) {
8601                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8602                         cps->which, gameMode);
8603             }
8604
8605             SendToProgram("undo\n", cps);
8606           }
8607           return;
8608         }
8609
8610         switch (gameMode) {
8611           case BeginningOfGame:
8612             /* Extra move from before last reset; ignore */
8613             if (appData.debugMode) {
8614                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8615             }
8616             return;
8617
8618           case EndOfGame:
8619           case IcsIdle:
8620           default:
8621             /* Extra move after we tried to stop.  The mode test is
8622                not a reliable way of detecting this problem, but it's
8623                the best we can do on engines that don't support ping.
8624             */
8625             if (appData.debugMode) {
8626                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8627                         cps->which, gameMode);
8628             }
8629             SendToProgram("undo\n", cps);
8630             return;
8631
8632           case MachinePlaysWhite:
8633           case IcsPlayingWhite:
8634             machineWhite = TRUE;
8635             break;
8636
8637           case MachinePlaysBlack:
8638           case IcsPlayingBlack:
8639             machineWhite = FALSE;
8640             break;
8641
8642           case TwoMachinesPlay:
8643             machineWhite = (cps->twoMachinesColor[0] == 'w');
8644             break;
8645         }
8646         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8647             if (appData.debugMode) {
8648                 fprintf(debugFP,
8649                         "Ignoring move out of turn by %s, gameMode %d"
8650                         ", forwardMost %d\n",
8651                         cps->which, gameMode, forwardMostMove);
8652             }
8653             return;
8654         }
8655
8656         if(cps->alphaRank) AlphaRank(machineMove, 4);
8657
8658         // [HGM] lion: (some very limited) support for Alien protocol
8659         killX = killY = -1;
8660         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8661             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8662             return;
8663         } else if(firstLeg[0]) { // there was a previous leg;
8664             // only support case where same piece makes two step (and don't even test that!)
8665             char buf[20], *p = machineMove+1, *q = buf+1, f;
8666             safeStrCpy(buf, machineMove, 20);
8667             while(isdigit(*q)) q++; // find start of to-square
8668             safeStrCpy(machineMove, firstLeg, 20);
8669             while(isdigit(*p)) p++;
8670             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8671             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8672             firstLeg[0] = NULLCHAR;
8673         }
8674
8675         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8676                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8677             /* Machine move could not be parsed; ignore it. */
8678           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8679                     machineMove, _(cps->which));
8680             DisplayMoveError(buf1);
8681             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8682                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8683             if (gameMode == TwoMachinesPlay) {
8684               GameEnds(machineWhite ? BlackWins : WhiteWins,
8685                        buf1, GE_XBOARD);
8686             }
8687             return;
8688         }
8689
8690         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8691         /* So we have to redo legality test with true e.p. status here,  */
8692         /* to make sure an illegal e.p. capture does not slip through,   */
8693         /* to cause a forfeit on a justified illegal-move complaint      */
8694         /* of the opponent.                                              */
8695         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8696            ChessMove moveType;
8697            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8698                              fromY, fromX, toY, toX, promoChar);
8699             if(moveType == IllegalMove) {
8700               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8701                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8702                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8703                            buf1, GE_XBOARD);
8704                 return;
8705            } else if(!appData.fischerCastling)
8706            /* [HGM] Kludge to handle engines that send FRC-style castling
8707               when they shouldn't (like TSCP-Gothic) */
8708            switch(moveType) {
8709              case WhiteASideCastleFR:
8710              case BlackASideCastleFR:
8711                toX+=2;
8712                currentMoveString[2]++;
8713                break;
8714              case WhiteHSideCastleFR:
8715              case BlackHSideCastleFR:
8716                toX--;
8717                currentMoveString[2]--;
8718                break;
8719              default: ; // nothing to do, but suppresses warning of pedantic compilers
8720            }
8721         }
8722         hintRequested = FALSE;
8723         lastHint[0] = NULLCHAR;
8724         bookRequested = FALSE;
8725         /* Program may be pondering now */
8726         cps->maybeThinking = TRUE;
8727         if (cps->sendTime == 2) cps->sendTime = 1;
8728         if (cps->offeredDraw) cps->offeredDraw--;
8729
8730         /* [AS] Save move info*/
8731         pvInfoList[ forwardMostMove ].score = programStats.score;
8732         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8733         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8734
8735         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8736
8737         /* Test suites abort the 'game' after one move */
8738         if(*appData.finger) {
8739            static FILE *f;
8740            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8741            if(!f) f = fopen(appData.finger, "w");
8742            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8743            else { DisplayFatalError("Bad output file", errno, 0); return; }
8744            free(fen);
8745            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8746         }
8747
8748         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8749         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8750             int count = 0;
8751
8752             while( count < adjudicateLossPlies ) {
8753                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8754
8755                 if( count & 1 ) {
8756                     score = -score; /* Flip score for winning side */
8757                 }
8758
8759                 if( score > appData.adjudicateLossThreshold ) {
8760                     break;
8761                 }
8762
8763                 count++;
8764             }
8765
8766             if( count >= adjudicateLossPlies ) {
8767                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8768
8769                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8770                     "Xboard adjudication",
8771                     GE_XBOARD );
8772
8773                 return;
8774             }
8775         }
8776
8777         if(Adjudicate(cps)) {
8778             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8779             return; // [HGM] adjudicate: for all automatic game ends
8780         }
8781
8782 #if ZIPPY
8783         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8784             first.initDone) {
8785           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8786                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8787                 SendToICS("draw ");
8788                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8789           }
8790           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8791           ics_user_moved = 1;
8792           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8793                 char buf[3*MSG_SIZ];
8794
8795                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8796                         programStats.score / 100.,
8797                         programStats.depth,
8798                         programStats.time / 100.,
8799                         (unsigned int)programStats.nodes,
8800                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8801                         programStats.movelist);
8802                 SendToICS(buf);
8803           }
8804         }
8805 #endif
8806
8807         /* [AS] Clear stats for next move */
8808         ClearProgramStats();
8809         thinkOutput[0] = NULLCHAR;
8810         hiddenThinkOutputState = 0;
8811
8812         bookHit = NULL;
8813         if (gameMode == TwoMachinesPlay) {
8814             /* [HGM] relaying draw offers moved to after reception of move */
8815             /* and interpreting offer as claim if it brings draw condition */
8816             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8817                 SendToProgram("draw\n", cps->other);
8818             }
8819             if (cps->other->sendTime) {
8820                 SendTimeRemaining(cps->other,
8821                                   cps->other->twoMachinesColor[0] == 'w');
8822             }
8823             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8824             if (firstMove && !bookHit) {
8825                 firstMove = FALSE;
8826                 if (cps->other->useColors) {
8827                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8828                 }
8829                 SendToProgram("go\n", cps->other);
8830             }
8831             cps->other->maybeThinking = TRUE;
8832         }
8833
8834         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8835
8836         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8837
8838         if (!pausing && appData.ringBellAfterMoves) {
8839             if(!roar) RingBell();
8840         }
8841
8842         /*
8843          * Reenable menu items that were disabled while
8844          * machine was thinking
8845          */
8846         if (gameMode != TwoMachinesPlay)
8847             SetUserThinkingEnables();
8848
8849         // [HGM] book: after book hit opponent has received move and is now in force mode
8850         // force the book reply into it, and then fake that it outputted this move by jumping
8851         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8852         if(bookHit) {
8853                 static char bookMove[MSG_SIZ]; // a bit generous?
8854
8855                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8856                 strcat(bookMove, bookHit);
8857                 message = bookMove;
8858                 cps = cps->other;
8859                 programStats.nodes = programStats.depth = programStats.time =
8860                 programStats.score = programStats.got_only_move = 0;
8861                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8862
8863                 if(cps->lastPing != cps->lastPong) {
8864                     savedMessage = message; // args for deferred call
8865                     savedState = cps;
8866                     ScheduleDelayedEvent(DeferredBookMove, 10);
8867                     return;
8868                 }
8869                 goto FakeBookMove;
8870         }
8871
8872         return;
8873     }
8874
8875     /* Set special modes for chess engines.  Later something general
8876      *  could be added here; for now there is just one kludge feature,
8877      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8878      *  when "xboard" is given as an interactive command.
8879      */
8880     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8881         cps->useSigint = FALSE;
8882         cps->useSigterm = FALSE;
8883     }
8884     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8885       ParseFeatures(message+8, cps);
8886       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8887     }
8888
8889     if (!strncmp(message, "setup ", 6) && 
8890         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8891           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8892                                         ) { // [HGM] allow first engine to define opening position
8893       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8894       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8895       *buf = NULLCHAR;
8896       if(sscanf(message, "setup (%s", buf) == 1) {
8897         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8898         ASSIGN(appData.pieceToCharTable, buf);
8899       }
8900       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8901       if(dummy >= 3) {
8902         while(message[s] && message[s++] != ' ');
8903         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8904            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8905             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8906             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8907           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8908           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8909           startedFromSetupPosition = FALSE;
8910         }
8911       }
8912       if(startedFromSetupPosition) return;
8913       ParseFEN(boards[0], &dummy, message+s, FALSE);
8914       DrawPosition(TRUE, boards[0]);
8915       CopyBoard(initialPosition, boards[0]);
8916       startedFromSetupPosition = TRUE;
8917       return;
8918     }
8919     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8920       ChessSquare piece = WhitePawn;
8921       char *p=buf2;
8922       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8923       piece += CharToPiece(*p) - WhitePawn;
8924       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8925       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8926       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8927       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8928       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8929       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8930                                                && gameInfo.variant != VariantGreat
8931                                                && gameInfo.variant != VariantFairy    ) return;
8932       if(piece < EmptySquare) {
8933         pieceDefs = TRUE;
8934         ASSIGN(pieceDesc[piece], buf1);
8935         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8936       }
8937       return;
8938     }
8939     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8940      * want this, I was asked to put it in, and obliged.
8941      */
8942     if (!strncmp(message, "setboard ", 9)) {
8943         Board initial_position;
8944
8945         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8946
8947         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8948             DisplayError(_("Bad FEN received from engine"), 0);
8949             return ;
8950         } else {
8951            Reset(TRUE, FALSE);
8952            CopyBoard(boards[0], initial_position);
8953            initialRulePlies = FENrulePlies;
8954            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8955            else gameMode = MachinePlaysBlack;
8956            DrawPosition(FALSE, boards[currentMove]);
8957         }
8958         return;
8959     }
8960
8961     /*
8962      * Look for communication commands
8963      */
8964     if (!strncmp(message, "telluser ", 9)) {
8965         if(message[9] == '\\' && message[10] == '\\')
8966             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8967         PlayTellSound();
8968         DisplayNote(message + 9);
8969         return;
8970     }
8971     if (!strncmp(message, "tellusererror ", 14)) {
8972         cps->userError = 1;
8973         if(message[14] == '\\' && message[15] == '\\')
8974             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8975         PlayTellSound();
8976         DisplayError(message + 14, 0);
8977         return;
8978     }
8979     if (!strncmp(message, "tellopponent ", 13)) {
8980       if (appData.icsActive) {
8981         if (loggedOn) {
8982           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8983           SendToICS(buf1);
8984         }
8985       } else {
8986         DisplayNote(message + 13);
8987       }
8988       return;
8989     }
8990     if (!strncmp(message, "tellothers ", 11)) {
8991       if (appData.icsActive) {
8992         if (loggedOn) {
8993           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8994           SendToICS(buf1);
8995         }
8996       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8997       return;
8998     }
8999     if (!strncmp(message, "tellall ", 8)) {
9000       if (appData.icsActive) {
9001         if (loggedOn) {
9002           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9003           SendToICS(buf1);
9004         }
9005       } else {
9006         DisplayNote(message + 8);
9007       }
9008       return;
9009     }
9010     if (strncmp(message, "warning", 7) == 0) {
9011         /* Undocumented feature, use tellusererror in new code */
9012         DisplayError(message, 0);
9013         return;
9014     }
9015     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9016         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9017         strcat(realname, " query");
9018         AskQuestion(realname, buf2, buf1, cps->pr);
9019         return;
9020     }
9021     /* Commands from the engine directly to ICS.  We don't allow these to be
9022      *  sent until we are logged on. Crafty kibitzes have been known to
9023      *  interfere with the login process.
9024      */
9025     if (loggedOn) {
9026         if (!strncmp(message, "tellics ", 8)) {
9027             SendToICS(message + 8);
9028             SendToICS("\n");
9029             return;
9030         }
9031         if (!strncmp(message, "tellicsnoalias ", 15)) {
9032             SendToICS(ics_prefix);
9033             SendToICS(message + 15);
9034             SendToICS("\n");
9035             return;
9036         }
9037         /* The following are for backward compatibility only */
9038         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9039             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9040             SendToICS(ics_prefix);
9041             SendToICS(message);
9042             SendToICS("\n");
9043             return;
9044         }
9045     }
9046     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9047         if(initPing == cps->lastPong) {
9048             if(gameInfo.variant == VariantUnknown) {
9049                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9050                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9051                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9052             }
9053             initPing = -1;
9054         }
9055         return;
9056     }
9057     if(!strncmp(message, "highlight ", 10)) {
9058         if(appData.testLegality && appData.markers) return;
9059         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9060         return;
9061     }
9062     if(!strncmp(message, "click ", 6)) {
9063         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9064         if(appData.testLegality || !appData.oneClick) return;
9065         sscanf(message+6, "%c%d%c", &f, &y, &c);
9066         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9067         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9068         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9069         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9070         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9071         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9072             LeftClick(Release, lastLeftX, lastLeftY);
9073         controlKey  = (c == ',');
9074         LeftClick(Press, x, y);
9075         LeftClick(Release, x, y);
9076         first.highlight = f;
9077         return;
9078     }
9079     /*
9080      * If the move is illegal, cancel it and redraw the board.
9081      * Also deal with other error cases.  Matching is rather loose
9082      * here to accommodate engines written before the spec.
9083      */
9084     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9085         strncmp(message, "Error", 5) == 0) {
9086         if (StrStr(message, "name") ||
9087             StrStr(message, "rating") || StrStr(message, "?") ||
9088             StrStr(message, "result") || StrStr(message, "board") ||
9089             StrStr(message, "bk") || StrStr(message, "computer") ||
9090             StrStr(message, "variant") || StrStr(message, "hint") ||
9091             StrStr(message, "random") || StrStr(message, "depth") ||
9092             StrStr(message, "accepted")) {
9093             return;
9094         }
9095         if (StrStr(message, "protover")) {
9096           /* Program is responding to input, so it's apparently done
9097              initializing, and this error message indicates it is
9098              protocol version 1.  So we don't need to wait any longer
9099              for it to initialize and send feature commands. */
9100           FeatureDone(cps, 1);
9101           cps->protocolVersion = 1;
9102           return;
9103         }
9104         cps->maybeThinking = FALSE;
9105
9106         if (StrStr(message, "draw")) {
9107             /* Program doesn't have "draw" command */
9108             cps->sendDrawOffers = 0;
9109             return;
9110         }
9111         if (cps->sendTime != 1 &&
9112             (StrStr(message, "time") || StrStr(message, "otim"))) {
9113           /* Program apparently doesn't have "time" or "otim" command */
9114           cps->sendTime = 0;
9115           return;
9116         }
9117         if (StrStr(message, "analyze")) {
9118             cps->analysisSupport = FALSE;
9119             cps->analyzing = FALSE;
9120 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9121             EditGameEvent(); // [HGM] try to preserve loaded game
9122             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9123             DisplayError(buf2, 0);
9124             return;
9125         }
9126         if (StrStr(message, "(no matching move)st")) {
9127           /* Special kludge for GNU Chess 4 only */
9128           cps->stKludge = TRUE;
9129           SendTimeControl(cps, movesPerSession, timeControl,
9130                           timeIncrement, appData.searchDepth,
9131                           searchTime);
9132           return;
9133         }
9134         if (StrStr(message, "(no matching move)sd")) {
9135           /* Special kludge for GNU Chess 4 only */
9136           cps->sdKludge = TRUE;
9137           SendTimeControl(cps, movesPerSession, timeControl,
9138                           timeIncrement, appData.searchDepth,
9139                           searchTime);
9140           return;
9141         }
9142         if (!StrStr(message, "llegal")) {
9143             return;
9144         }
9145         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9146             gameMode == IcsIdle) return;
9147         if (forwardMostMove <= backwardMostMove) return;
9148         if (pausing) PauseEvent();
9149       if(appData.forceIllegal) {
9150             // [HGM] illegal: machine refused move; force position after move into it
9151           SendToProgram("force\n", cps);
9152           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9153                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9154                 // when black is to move, while there might be nothing on a2 or black
9155                 // might already have the move. So send the board as if white has the move.
9156                 // But first we must change the stm of the engine, as it refused the last move
9157                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9158                 if(WhiteOnMove(forwardMostMove)) {
9159                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9160                     SendBoard(cps, forwardMostMove); // kludgeless board
9161                 } else {
9162                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9163                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9164                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9165                 }
9166           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9167             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9168                  gameMode == TwoMachinesPlay)
9169               SendToProgram("go\n", cps);
9170             return;
9171       } else
9172         if (gameMode == PlayFromGameFile) {
9173             /* Stop reading this game file */
9174             gameMode = EditGame;
9175             ModeHighlight();
9176         }
9177         /* [HGM] illegal-move claim should forfeit game when Xboard */
9178         /* only passes fully legal moves                            */
9179         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9180             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9181                                 "False illegal-move claim", GE_XBOARD );
9182             return; // do not take back move we tested as valid
9183         }
9184         currentMove = forwardMostMove-1;
9185         DisplayMove(currentMove-1); /* before DisplayMoveError */
9186         SwitchClocks(forwardMostMove-1); // [HGM] race
9187         DisplayBothClocks();
9188         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9189                 parseList[currentMove], _(cps->which));
9190         DisplayMoveError(buf1);
9191         DrawPosition(FALSE, boards[currentMove]);
9192
9193         SetUserThinkingEnables();
9194         return;
9195     }
9196     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9197         /* Program has a broken "time" command that
9198            outputs a string not ending in newline.
9199            Don't use it. */
9200         cps->sendTime = 0;
9201     }
9202     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9203         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9204             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9205     }
9206
9207     /*
9208      * If chess program startup fails, exit with an error message.
9209      * Attempts to recover here are futile. [HGM] Well, we try anyway
9210      */
9211     if ((StrStr(message, "unknown host") != NULL)
9212         || (StrStr(message, "No remote directory") != NULL)
9213         || (StrStr(message, "not found") != NULL)
9214         || (StrStr(message, "No such file") != NULL)
9215         || (StrStr(message, "can't alloc") != NULL)
9216         || (StrStr(message, "Permission denied") != NULL)) {
9217
9218         cps->maybeThinking = FALSE;
9219         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9220                 _(cps->which), cps->program, cps->host, message);
9221         RemoveInputSource(cps->isr);
9222         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9223             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9224             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9225         }
9226         return;
9227     }
9228
9229     /*
9230      * Look for hint output
9231      */
9232     if (sscanf(message, "Hint: %s", buf1) == 1) {
9233         if (cps == &first && hintRequested) {
9234             hintRequested = FALSE;
9235             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9236                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9237                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9238                                     PosFlags(forwardMostMove),
9239                                     fromY, fromX, toY, toX, promoChar, buf1);
9240                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9241                 DisplayInformation(buf2);
9242             } else {
9243                 /* Hint move could not be parsed!? */
9244               snprintf(buf2, sizeof(buf2),
9245                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9246                         buf1, _(cps->which));
9247                 DisplayError(buf2, 0);
9248             }
9249         } else {
9250           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9251         }
9252         return;
9253     }
9254
9255     /*
9256      * Ignore other messages if game is not in progress
9257      */
9258     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9259         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9260
9261     /*
9262      * look for win, lose, draw, or draw offer
9263      */
9264     if (strncmp(message, "1-0", 3) == 0) {
9265         char *p, *q, *r = "";
9266         p = strchr(message, '{');
9267         if (p) {
9268             q = strchr(p, '}');
9269             if (q) {
9270                 *q = NULLCHAR;
9271                 r = p + 1;
9272             }
9273         }
9274         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9275         return;
9276     } else if (strncmp(message, "0-1", 3) == 0) {
9277         char *p, *q, *r = "";
9278         p = strchr(message, '{');
9279         if (p) {
9280             q = strchr(p, '}');
9281             if (q) {
9282                 *q = NULLCHAR;
9283                 r = p + 1;
9284             }
9285         }
9286         /* Kludge for Arasan 4.1 bug */
9287         if (strcmp(r, "Black resigns") == 0) {
9288             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9289             return;
9290         }
9291         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9292         return;
9293     } else if (strncmp(message, "1/2", 3) == 0) {
9294         char *p, *q, *r = "";
9295         p = strchr(message, '{');
9296         if (p) {
9297             q = strchr(p, '}');
9298             if (q) {
9299                 *q = NULLCHAR;
9300                 r = p + 1;
9301             }
9302         }
9303
9304         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9305         return;
9306
9307     } else if (strncmp(message, "White resign", 12) == 0) {
9308         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9309         return;
9310     } else if (strncmp(message, "Black resign", 12) == 0) {
9311         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9312         return;
9313     } else if (strncmp(message, "White matches", 13) == 0 ||
9314                strncmp(message, "Black matches", 13) == 0   ) {
9315         /* [HGM] ignore GNUShogi noises */
9316         return;
9317     } else if (strncmp(message, "White", 5) == 0 &&
9318                message[5] != '(' &&
9319                StrStr(message, "Black") == NULL) {
9320         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9321         return;
9322     } else if (strncmp(message, "Black", 5) == 0 &&
9323                message[5] != '(') {
9324         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9325         return;
9326     } else if (strcmp(message, "resign") == 0 ||
9327                strcmp(message, "computer resigns") == 0) {
9328         switch (gameMode) {
9329           case MachinePlaysBlack:
9330           case IcsPlayingBlack:
9331             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9332             break;
9333           case MachinePlaysWhite:
9334           case IcsPlayingWhite:
9335             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9336             break;
9337           case TwoMachinesPlay:
9338             if (cps->twoMachinesColor[0] == 'w')
9339               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9340             else
9341               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9342             break;
9343           default:
9344             /* can't happen */
9345             break;
9346         }
9347         return;
9348     } else if (strncmp(message, "opponent mates", 14) == 0) {
9349         switch (gameMode) {
9350           case MachinePlaysBlack:
9351           case IcsPlayingBlack:
9352             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9353             break;
9354           case MachinePlaysWhite:
9355           case IcsPlayingWhite:
9356             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9357             break;
9358           case TwoMachinesPlay:
9359             if (cps->twoMachinesColor[0] == 'w')
9360               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9361             else
9362               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9363             break;
9364           default:
9365             /* can't happen */
9366             break;
9367         }
9368         return;
9369     } else if (strncmp(message, "computer mates", 14) == 0) {
9370         switch (gameMode) {
9371           case MachinePlaysBlack:
9372           case IcsPlayingBlack:
9373             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9374             break;
9375           case MachinePlaysWhite:
9376           case IcsPlayingWhite:
9377             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9378             break;
9379           case TwoMachinesPlay:
9380             if (cps->twoMachinesColor[0] == 'w')
9381               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9382             else
9383               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9384             break;
9385           default:
9386             /* can't happen */
9387             break;
9388         }
9389         return;
9390     } else if (strncmp(message, "checkmate", 9) == 0) {
9391         if (WhiteOnMove(forwardMostMove)) {
9392             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9393         } else {
9394             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9395         }
9396         return;
9397     } else if (strstr(message, "Draw") != NULL ||
9398                strstr(message, "game is a draw") != NULL) {
9399         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9400         return;
9401     } else if (strstr(message, "offer") != NULL &&
9402                strstr(message, "draw") != NULL) {
9403 #if ZIPPY
9404         if (appData.zippyPlay && first.initDone) {
9405             /* Relay offer to ICS */
9406             SendToICS(ics_prefix);
9407             SendToICS("draw\n");
9408         }
9409 #endif
9410         cps->offeredDraw = 2; /* valid until this engine moves twice */
9411         if (gameMode == TwoMachinesPlay) {
9412             if (cps->other->offeredDraw) {
9413                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9414             /* [HGM] in two-machine mode we delay relaying draw offer      */
9415             /* until after we also have move, to see if it is really claim */
9416             }
9417         } else if (gameMode == MachinePlaysWhite ||
9418                    gameMode == MachinePlaysBlack) {
9419           if (userOfferedDraw) {
9420             DisplayInformation(_("Machine accepts your draw offer"));
9421             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9422           } else {
9423             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9424           }
9425         }
9426     }
9427
9428
9429     /*
9430      * Look for thinking output
9431      */
9432     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9433           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9434                                 ) {
9435         int plylev, mvleft, mvtot, curscore, time;
9436         char mvname[MOVE_LEN];
9437         u64 nodes; // [DM]
9438         char plyext;
9439         int ignore = FALSE;
9440         int prefixHint = FALSE;
9441         mvname[0] = NULLCHAR;
9442
9443         switch (gameMode) {
9444           case MachinePlaysBlack:
9445           case IcsPlayingBlack:
9446             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9447             break;
9448           case MachinePlaysWhite:
9449           case IcsPlayingWhite:
9450             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9451             break;
9452           case AnalyzeMode:
9453           case AnalyzeFile:
9454             break;
9455           case IcsObserving: /* [DM] icsEngineAnalyze */
9456             if (!appData.icsEngineAnalyze) ignore = TRUE;
9457             break;
9458           case TwoMachinesPlay:
9459             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9460                 ignore = TRUE;
9461             }
9462             break;
9463           default:
9464             ignore = TRUE;
9465             break;
9466         }
9467
9468         if (!ignore) {
9469             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9470             buf1[0] = NULLCHAR;
9471             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9472                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9473
9474                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9475                     nodes += u64Const(0x100000000);
9476
9477                 if (plyext != ' ' && plyext != '\t') {
9478                     time *= 100;
9479                 }
9480
9481                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9482                 if( cps->scoreIsAbsolute &&
9483                     ( gameMode == MachinePlaysBlack ||
9484                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9485                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9486                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9487                      !WhiteOnMove(currentMove)
9488                     ) )
9489                 {
9490                     curscore = -curscore;
9491                 }
9492
9493                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9494
9495                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9496                         char buf[MSG_SIZ];
9497                         FILE *f;
9498                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9499                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9500                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9501                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9502                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9503                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9504                                 fclose(f);
9505                         }
9506                         else
9507                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9508                           DisplayError(_("failed writing PV"), 0);
9509                 }
9510
9511                 tempStats.depth = plylev;
9512                 tempStats.nodes = nodes;
9513                 tempStats.time = time;
9514                 tempStats.score = curscore;
9515                 tempStats.got_only_move = 0;
9516
9517                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9518                         int ticklen;
9519
9520                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9521                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9522                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9523                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9524                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9525                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9526                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9527                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9528                 }
9529
9530                 /* Buffer overflow protection */
9531                 if (pv[0] != NULLCHAR) {
9532                     if (strlen(pv) >= sizeof(tempStats.movelist)
9533                         && appData.debugMode) {
9534                         fprintf(debugFP,
9535                                 "PV is too long; using the first %u bytes.\n",
9536                                 (unsigned) sizeof(tempStats.movelist) - 1);
9537                     }
9538
9539                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9540                 } else {
9541                     sprintf(tempStats.movelist, " no PV\n");
9542                 }
9543
9544                 if (tempStats.seen_stat) {
9545                     tempStats.ok_to_send = 1;
9546                 }
9547
9548                 if (strchr(tempStats.movelist, '(') != NULL) {
9549                     tempStats.line_is_book = 1;
9550                     tempStats.nr_moves = 0;
9551                     tempStats.moves_left = 0;
9552                 } else {
9553                     tempStats.line_is_book = 0;
9554                 }
9555
9556                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9557                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9558
9559                 SendProgramStatsToFrontend( cps, &tempStats );
9560
9561                 /*
9562                     [AS] Protect the thinkOutput buffer from overflow... this
9563                     is only useful if buf1 hasn't overflowed first!
9564                 */
9565                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9566                          plylev,
9567                          (gameMode == TwoMachinesPlay ?
9568                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9569                          ((double) curscore) / 100.0,
9570                          prefixHint ? lastHint : "",
9571                          prefixHint ? " " : "" );
9572
9573                 if( buf1[0] != NULLCHAR ) {
9574                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9575
9576                     if( strlen(pv) > max_len ) {
9577                         if( appData.debugMode) {
9578                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9579                         }
9580                         pv[max_len+1] = '\0';
9581                     }
9582
9583                     strcat( thinkOutput, pv);
9584                 }
9585
9586                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9587                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9588                     DisplayMove(currentMove - 1);
9589                 }
9590                 return;
9591
9592             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9593                 /* crafty (9.25+) says "(only move) <move>"
9594                  * if there is only 1 legal move
9595                  */
9596                 sscanf(p, "(only move) %s", buf1);
9597                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9598                 sprintf(programStats.movelist, "%s (only move)", buf1);
9599                 programStats.depth = 1;
9600                 programStats.nr_moves = 1;
9601                 programStats.moves_left = 1;
9602                 programStats.nodes = 1;
9603                 programStats.time = 1;
9604                 programStats.got_only_move = 1;
9605
9606                 /* Not really, but we also use this member to
9607                    mean "line isn't going to change" (Crafty
9608                    isn't searching, so stats won't change) */
9609                 programStats.line_is_book = 1;
9610
9611                 SendProgramStatsToFrontend( cps, &programStats );
9612
9613                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9614                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9615                     DisplayMove(currentMove - 1);
9616                 }
9617                 return;
9618             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9619                               &time, &nodes, &plylev, &mvleft,
9620                               &mvtot, mvname) >= 5) {
9621                 /* The stat01: line is from Crafty (9.29+) in response
9622                    to the "." command */
9623                 programStats.seen_stat = 1;
9624                 cps->maybeThinking = TRUE;
9625
9626                 if (programStats.got_only_move || !appData.periodicUpdates)
9627                   return;
9628
9629                 programStats.depth = plylev;
9630                 programStats.time = time;
9631                 programStats.nodes = nodes;
9632                 programStats.moves_left = mvleft;
9633                 programStats.nr_moves = mvtot;
9634                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9635                 programStats.ok_to_send = 1;
9636                 programStats.movelist[0] = '\0';
9637
9638                 SendProgramStatsToFrontend( cps, &programStats );
9639
9640                 return;
9641
9642             } else if (strncmp(message,"++",2) == 0) {
9643                 /* Crafty 9.29+ outputs this */
9644                 programStats.got_fail = 2;
9645                 return;
9646
9647             } else if (strncmp(message,"--",2) == 0) {
9648                 /* Crafty 9.29+ outputs this */
9649                 programStats.got_fail = 1;
9650                 return;
9651
9652             } else if (thinkOutput[0] != NULLCHAR &&
9653                        strncmp(message, "    ", 4) == 0) {
9654                 unsigned message_len;
9655
9656                 p = message;
9657                 while (*p && *p == ' ') p++;
9658
9659                 message_len = strlen( p );
9660
9661                 /* [AS] Avoid buffer overflow */
9662                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9663                     strcat(thinkOutput, " ");
9664                     strcat(thinkOutput, p);
9665                 }
9666
9667                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9668                     strcat(programStats.movelist, " ");
9669                     strcat(programStats.movelist, p);
9670                 }
9671
9672                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9673                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9674                     DisplayMove(currentMove - 1);
9675                 }
9676                 return;
9677             }
9678         }
9679         else {
9680             buf1[0] = NULLCHAR;
9681
9682             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9683                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9684             {
9685                 ChessProgramStats cpstats;
9686
9687                 if (plyext != ' ' && plyext != '\t') {
9688                     time *= 100;
9689                 }
9690
9691                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9692                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9693                     curscore = -curscore;
9694                 }
9695
9696                 cpstats.depth = plylev;
9697                 cpstats.nodes = nodes;
9698                 cpstats.time = time;
9699                 cpstats.score = curscore;
9700                 cpstats.got_only_move = 0;
9701                 cpstats.movelist[0] = '\0';
9702
9703                 if (buf1[0] != NULLCHAR) {
9704                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9705                 }
9706
9707                 cpstats.ok_to_send = 0;
9708                 cpstats.line_is_book = 0;
9709                 cpstats.nr_moves = 0;
9710                 cpstats.moves_left = 0;
9711
9712                 SendProgramStatsToFrontend( cps, &cpstats );
9713             }
9714         }
9715     }
9716 }
9717
9718
9719 /* Parse a game score from the character string "game", and
9720    record it as the history of the current game.  The game
9721    score is NOT assumed to start from the standard position.
9722    The display is not updated in any way.
9723    */
9724 void
9725 ParseGameHistory (char *game)
9726 {
9727     ChessMove moveType;
9728     int fromX, fromY, toX, toY, boardIndex;
9729     char promoChar;
9730     char *p, *q;
9731     char buf[MSG_SIZ];
9732
9733     if (appData.debugMode)
9734       fprintf(debugFP, "Parsing game history: %s\n", game);
9735
9736     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9737     gameInfo.site = StrSave(appData.icsHost);
9738     gameInfo.date = PGNDate();
9739     gameInfo.round = StrSave("-");
9740
9741     /* Parse out names of players */
9742     while (*game == ' ') game++;
9743     p = buf;
9744     while (*game != ' ') *p++ = *game++;
9745     *p = NULLCHAR;
9746     gameInfo.white = StrSave(buf);
9747     while (*game == ' ') game++;
9748     p = buf;
9749     while (*game != ' ' && *game != '\n') *p++ = *game++;
9750     *p = NULLCHAR;
9751     gameInfo.black = StrSave(buf);
9752
9753     /* Parse moves */
9754     boardIndex = blackPlaysFirst ? 1 : 0;
9755     yynewstr(game);
9756     for (;;) {
9757         yyboardindex = boardIndex;
9758         moveType = (ChessMove) Myylex();
9759         switch (moveType) {
9760           case IllegalMove:             /* maybe suicide chess, etc. */
9761   if (appData.debugMode) {
9762     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9763     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9764     setbuf(debugFP, NULL);
9765   }
9766           case WhitePromotion:
9767           case BlackPromotion:
9768           case WhiteNonPromotion:
9769           case BlackNonPromotion:
9770           case NormalMove:
9771           case FirstLeg:
9772           case WhiteCapturesEnPassant:
9773           case BlackCapturesEnPassant:
9774           case WhiteKingSideCastle:
9775           case WhiteQueenSideCastle:
9776           case BlackKingSideCastle:
9777           case BlackQueenSideCastle:
9778           case WhiteKingSideCastleWild:
9779           case WhiteQueenSideCastleWild:
9780           case BlackKingSideCastleWild:
9781           case BlackQueenSideCastleWild:
9782           /* PUSH Fabien */
9783           case WhiteHSideCastleFR:
9784           case WhiteASideCastleFR:
9785           case BlackHSideCastleFR:
9786           case BlackASideCastleFR:
9787           /* POP Fabien */
9788             fromX = currentMoveString[0] - AAA;
9789             fromY = currentMoveString[1] - ONE;
9790             toX = currentMoveString[2] - AAA;
9791             toY = currentMoveString[3] - ONE;
9792             promoChar = currentMoveString[4];
9793             break;
9794           case WhiteDrop:
9795           case BlackDrop:
9796             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9797             fromX = moveType == WhiteDrop ?
9798               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9799             (int) CharToPiece(ToLower(currentMoveString[0]));
9800             fromY = DROP_RANK;
9801             toX = currentMoveString[2] - AAA;
9802             toY = currentMoveString[3] - ONE;
9803             promoChar = NULLCHAR;
9804             break;
9805           case AmbiguousMove:
9806             /* bug? */
9807             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9808   if (appData.debugMode) {
9809     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9810     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9811     setbuf(debugFP, NULL);
9812   }
9813             DisplayError(buf, 0);
9814             return;
9815           case ImpossibleMove:
9816             /* bug? */
9817             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9818   if (appData.debugMode) {
9819     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9820     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9821     setbuf(debugFP, NULL);
9822   }
9823             DisplayError(buf, 0);
9824             return;
9825           case EndOfFile:
9826             if (boardIndex < backwardMostMove) {
9827                 /* Oops, gap.  How did that happen? */
9828                 DisplayError(_("Gap in move list"), 0);
9829                 return;
9830             }
9831             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9832             if (boardIndex > forwardMostMove) {
9833                 forwardMostMove = boardIndex;
9834             }
9835             return;
9836           case ElapsedTime:
9837             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9838                 strcat(parseList[boardIndex-1], " ");
9839                 strcat(parseList[boardIndex-1], yy_text);
9840             }
9841             continue;
9842           case Comment:
9843           case PGNTag:
9844           case NAG:
9845           default:
9846             /* ignore */
9847             continue;
9848           case WhiteWins:
9849           case BlackWins:
9850           case GameIsDrawn:
9851           case GameUnfinished:
9852             if (gameMode == IcsExamining) {
9853                 if (boardIndex < backwardMostMove) {
9854                     /* Oops, gap.  How did that happen? */
9855                     return;
9856                 }
9857                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9858                 return;
9859             }
9860             gameInfo.result = moveType;
9861             p = strchr(yy_text, '{');
9862             if (p == NULL) p = strchr(yy_text, '(');
9863             if (p == NULL) {
9864                 p = yy_text;
9865                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9866             } else {
9867                 q = strchr(p, *p == '{' ? '}' : ')');
9868                 if (q != NULL) *q = NULLCHAR;
9869                 p++;
9870             }
9871             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9872             gameInfo.resultDetails = StrSave(p);
9873             continue;
9874         }
9875         if (boardIndex >= forwardMostMove &&
9876             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9877             backwardMostMove = blackPlaysFirst ? 1 : 0;
9878             return;
9879         }
9880         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9881                                  fromY, fromX, toY, toX, promoChar,
9882                                  parseList[boardIndex]);
9883         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9884         /* currentMoveString is set as a side-effect of yylex */
9885         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9886         strcat(moveList[boardIndex], "\n");
9887         boardIndex++;
9888         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9889         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9890           case MT_NONE:
9891           case MT_STALEMATE:
9892           default:
9893             break;
9894           case MT_CHECK:
9895             if(!IS_SHOGI(gameInfo.variant))
9896                 strcat(parseList[boardIndex - 1], "+");
9897             break;
9898           case MT_CHECKMATE:
9899           case MT_STAINMATE:
9900             strcat(parseList[boardIndex - 1], "#");
9901             break;
9902         }
9903     }
9904 }
9905
9906
9907 /* Apply a move to the given board  */
9908 void
9909 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9910 {
9911   ChessSquare captured = board[toY][toX], piece, pawn, king, killed; int p, rookX, oldEP, epRank, berolina = 0;
9912   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9913
9914     /* [HGM] compute & store e.p. status and castling rights for new position */
9915     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9916
9917       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9918       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
9919       board[EP_STATUS] = EP_NONE;
9920       board[EP_FILE] = board[EP_RANK] = 100;
9921
9922   if (fromY == DROP_RANK) {
9923         /* must be first */
9924         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9925             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9926             return;
9927         }
9928         piece = board[toY][toX] = (ChessSquare) fromX;
9929   } else {
9930 //      ChessSquare victim;
9931       int i;
9932
9933       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9934 //           victim = board[killY][killX],
9935            killed = board[killY][killX],
9936            board[killY][killX] = EmptySquare,
9937            board[EP_STATUS] = EP_CAPTURE;
9938
9939       if( board[toY][toX] != EmptySquare ) {
9940            board[EP_STATUS] = EP_CAPTURE;
9941            if( (fromX != toX || fromY != toY) && // not igui!
9942                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9943                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9944                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9945            }
9946       }
9947
9948       pawn = board[fromY][fromX];
9949       if( pawn == WhiteLance || pawn == BlackLance ) {
9950            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
9951                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
9952                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
9953            }
9954       }
9955       if( pawn == WhitePawn ) {
9956            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9957                board[EP_STATUS] = EP_PAWN_MOVE;
9958            if( toY-fromY>=2) {
9959                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
9960                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9961                         gameInfo.variant != VariantBerolina || toX < fromX)
9962                       board[EP_STATUS] = toX | berolina;
9963                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9964                         gameInfo.variant != VariantBerolina || toX > fromX)
9965                       board[EP_STATUS] = toX;
9966            }
9967       } else
9968       if( pawn == BlackPawn ) {
9969            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9970                board[EP_STATUS] = EP_PAWN_MOVE;
9971            if( toY-fromY<= -2) {
9972                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
9973                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9974                         gameInfo.variant != VariantBerolina || toX < fromX)
9975                       board[EP_STATUS] = toX | berolina;
9976                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9977                         gameInfo.variant != VariantBerolina || toX > fromX)
9978                       board[EP_STATUS] = toX;
9979            }
9980        }
9981
9982        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
9983        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
9984        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
9985        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
9986
9987        for(i=0; i<nrCastlingRights; i++) {
9988            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9989               board[CASTLING][i] == toX   && castlingRank[i] == toY
9990              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9991        }
9992
9993        if(gameInfo.variant == VariantSChess) { // update virginity
9994            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9995            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9996            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9997            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9998        }
9999
10000      if (fromX == toX && fromY == toY) return;
10001
10002      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10003      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10004      if(gameInfo.variant == VariantKnightmate)
10005          king += (int) WhiteUnicorn - (int) WhiteKing;
10006
10007     if(piece != WhiteKing && piece != BlackKing && pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10008        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and captures own
10009         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10010         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10011         board[EP_STATUS] = EP_NONE; // capture was fake!
10012     } else
10013     /* Code added by Tord: */
10014     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10015     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10016         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10017       board[EP_STATUS] = EP_NONE; // capture was fake!
10018       board[fromY][fromX] = EmptySquare;
10019       board[toY][toX] = EmptySquare;
10020       if((toX > fromX) != (piece == WhiteRook)) {
10021         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10022       } else {
10023         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10024       }
10025     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10026                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10027       board[EP_STATUS] = EP_NONE;
10028       board[fromY][fromX] = EmptySquare;
10029       board[toY][toX] = EmptySquare;
10030       if((toX > fromX) != (piece == BlackRook)) {
10031         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10032       } else {
10033         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10034       }
10035     /* End of code added by Tord */
10036
10037     } else if (board[fromY][fromX] == king
10038         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10039         && toY == fromY && toX > fromX+1) {
10040         board[fromY][fromX] = EmptySquare;
10041         board[toY][toX] = king;
10042         for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
10043         board[toY][toX-1] = board[fromY][rookX];
10044         board[fromY][rookX] = EmptySquare;
10045     } else if (board[fromY][fromX] == king
10046         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10047                && toY == fromY && toX < fromX-1) {
10048         board[fromY][fromX] = EmptySquare;
10049         board[toY][toX] = king;
10050         for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
10051         board[toY][toX+1] = board[fromY][rookX];
10052         board[fromY][rookX] = EmptySquare;
10053     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10054                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10055                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10056                ) {
10057         /* white pawn promotion */
10058         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10059         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10060             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10061         board[fromY][fromX] = EmptySquare;
10062     } else if ((fromY >= BOARD_HEIGHT>>1)
10063                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10064                && (toX != fromX)
10065                && gameInfo.variant != VariantXiangqi
10066                && gameInfo.variant != VariantBerolina
10067                && (pawn == WhitePawn)
10068                && (board[toY][toX] == EmptySquare)) {
10069         board[fromY][fromX] = EmptySquare;
10070         board[toY][toX] = piece;
10071         if(toY == epRank - 128 + 1)
10072             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10073         else
10074             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10075     } else if ((fromY == BOARD_HEIGHT-4)
10076                && (toX == fromX)
10077                && gameInfo.variant == VariantBerolina
10078                && (board[fromY][fromX] == WhitePawn)
10079                && (board[toY][toX] == EmptySquare)) {
10080         board[fromY][fromX] = EmptySquare;
10081         board[toY][toX] = WhitePawn;
10082         if(oldEP & EP_BEROLIN_A) {
10083                 captured = board[fromY][fromX-1];
10084                 board[fromY][fromX-1] = EmptySquare;
10085         }else{  captured = board[fromY][fromX+1];
10086                 board[fromY][fromX+1] = EmptySquare;
10087         }
10088     } else if (board[fromY][fromX] == king
10089         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10090                && toY == fromY && toX > fromX+1) {
10091         board[fromY][fromX] = EmptySquare;
10092         board[toY][toX] = king;
10093         for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
10094         board[toY][toX-1] = board[fromY][rookX];
10095         board[fromY][rookX] = EmptySquare;
10096     } else if (board[fromY][fromX] == king
10097         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10098                && toY == fromY && toX < fromX-1) {
10099         board[fromY][fromX] = EmptySquare;
10100         board[toY][toX] = king;
10101         for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
10102         board[toY][toX+1] = board[fromY][rookX];
10103         board[fromY][rookX] = EmptySquare;
10104     } else if (fromY == 7 && fromX == 3
10105                && board[fromY][fromX] == BlackKing
10106                && toY == 7 && toX == 5) {
10107         board[fromY][fromX] = EmptySquare;
10108         board[toY][toX] = BlackKing;
10109         board[fromY][7] = EmptySquare;
10110         board[toY][4] = BlackRook;
10111     } else if (fromY == 7 && fromX == 3
10112                && board[fromY][fromX] == BlackKing
10113                && toY == 7 && toX == 1) {
10114         board[fromY][fromX] = EmptySquare;
10115         board[toY][toX] = BlackKing;
10116         board[fromY][0] = EmptySquare;
10117         board[toY][2] = BlackRook;
10118     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10119                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10120                && toY < promoRank && promoChar
10121                ) {
10122         /* black pawn promotion */
10123         board[toY][toX] = CharToPiece(ToLower(promoChar));
10124         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10125             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10126         board[fromY][fromX] = EmptySquare;
10127     } else if ((fromY < BOARD_HEIGHT>>1)
10128                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10129                && (toX != fromX)
10130                && gameInfo.variant != VariantXiangqi
10131                && gameInfo.variant != VariantBerolina
10132                && (pawn == BlackPawn)
10133                && (board[toY][toX] == EmptySquare)) {
10134         board[fromY][fromX] = EmptySquare;
10135         board[toY][toX] = piece;
10136         if(toY == epRank - 128 - 1)
10137             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10138         else
10139             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10140     } else if ((fromY == 3)
10141                && (toX == fromX)
10142                && gameInfo.variant == VariantBerolina
10143                && (board[fromY][fromX] == BlackPawn)
10144                && (board[toY][toX] == EmptySquare)) {
10145         board[fromY][fromX] = EmptySquare;
10146         board[toY][toX] = BlackPawn;
10147         if(oldEP & EP_BEROLIN_A) {
10148                 captured = board[fromY][fromX-1];
10149                 board[fromY][fromX-1] = EmptySquare;
10150         }else{  captured = board[fromY][fromX+1];
10151                 board[fromY][fromX+1] = EmptySquare;
10152         }
10153     } else {
10154         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10155         board[fromY][fromX] = EmptySquare;
10156         board[toY][toX] = piece;
10157     }
10158   }
10159
10160     if (gameInfo.holdingsWidth != 0) {
10161
10162       /* !!A lot more code needs to be written to support holdings  */
10163       /* [HGM] OK, so I have written it. Holdings are stored in the */
10164       /* penultimate board files, so they are automaticlly stored   */
10165       /* in the game history.                                       */
10166       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10167                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10168         /* Delete from holdings, by decreasing count */
10169         /* and erasing image if necessary            */
10170         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10171         if(p < (int) BlackPawn) { /* white drop */
10172              p -= (int)WhitePawn;
10173                  p = PieceToNumber((ChessSquare)p);
10174              if(p >= gameInfo.holdingsSize) p = 0;
10175              if(--board[p][BOARD_WIDTH-2] <= 0)
10176                   board[p][BOARD_WIDTH-1] = EmptySquare;
10177              if((int)board[p][BOARD_WIDTH-2] < 0)
10178                         board[p][BOARD_WIDTH-2] = 0;
10179         } else {                  /* black drop */
10180              p -= (int)BlackPawn;
10181                  p = PieceToNumber((ChessSquare)p);
10182              if(p >= gameInfo.holdingsSize) p = 0;
10183              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10184                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10185              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10186                         board[BOARD_HEIGHT-1-p][1] = 0;
10187         }
10188       }
10189       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10190           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10191         /* [HGM] holdings: Add to holdings, if holdings exist */
10192         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10193                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10194                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10195         }
10196         p = (int) captured;
10197         if (p >= (int) BlackPawn) {
10198           p -= (int)BlackPawn;
10199           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10200                   /* Restore shogi-promoted piece to its original  first */
10201                   captured = (ChessSquare) (DEMOTED captured);
10202                   p = DEMOTED p;
10203           }
10204           p = PieceToNumber((ChessSquare)p);
10205           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10206           board[p][BOARD_WIDTH-2]++;
10207           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10208         } else {
10209           p -= (int)WhitePawn;
10210           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10211                   captured = (ChessSquare) (DEMOTED captured);
10212                   p = DEMOTED p;
10213           }
10214           p = PieceToNumber((ChessSquare)p);
10215           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10216           board[BOARD_HEIGHT-1-p][1]++;
10217           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10218         }
10219       }
10220     } else if (gameInfo.variant == VariantAtomic) {
10221       if (captured != EmptySquare) {
10222         int y, x;
10223         for (y = toY-1; y <= toY+1; y++) {
10224           for (x = toX-1; x <= toX+1; x++) {
10225             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10226                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10227               board[y][x] = EmptySquare;
10228             }
10229           }
10230         }
10231         board[toY][toX] = EmptySquare;
10232       }
10233     }
10234
10235     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10236         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10237     } else
10238     if(promoChar == '+') {
10239         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10240         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10241         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10242           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10243     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10244         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10245         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10246            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10247         board[toY][toX] = newPiece;
10248     }
10249     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10250                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10251         // [HGM] superchess: take promotion piece out of holdings
10252         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10253         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10254             if(!--board[k][BOARD_WIDTH-2])
10255                 board[k][BOARD_WIDTH-1] = EmptySquare;
10256         } else {
10257             if(!--board[BOARD_HEIGHT-1-k][1])
10258                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10259         }
10260     }
10261 }
10262
10263 /* Updates forwardMostMove */
10264 void
10265 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10266 {
10267     int x = toX, y = toY;
10268     char *s = parseList[forwardMostMove];
10269     ChessSquare p = boards[forwardMostMove][toY][toX];
10270 //    forwardMostMove++; // [HGM] bare: moved downstream
10271
10272     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10273     (void) CoordsToAlgebraic(boards[forwardMostMove],
10274                              PosFlags(forwardMostMove),
10275                              fromY, fromX, y, x, promoChar,
10276                              s);
10277     if(killX >= 0 && killY >= 0)
10278         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10279
10280     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10281         int timeLeft; static int lastLoadFlag=0; int king, piece;
10282         piece = boards[forwardMostMove][fromY][fromX];
10283         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10284         if(gameInfo.variant == VariantKnightmate)
10285             king += (int) WhiteUnicorn - (int) WhiteKing;
10286         if(forwardMostMove == 0) {
10287             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10288                 fprintf(serverMoves, "%s;", UserName());
10289             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10290                 fprintf(serverMoves, "%s;", second.tidy);
10291             fprintf(serverMoves, "%s;", first.tidy);
10292             if(gameMode == MachinePlaysWhite)
10293                 fprintf(serverMoves, "%s;", UserName());
10294             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10295                 fprintf(serverMoves, "%s;", second.tidy);
10296         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10297         lastLoadFlag = loadFlag;
10298         // print base move
10299         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10300         // print castling suffix
10301         if( toY == fromY && piece == king ) {
10302             if(toX-fromX > 1)
10303                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10304             if(fromX-toX >1)
10305                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10306         }
10307         // e.p. suffix
10308         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10309              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10310              boards[forwardMostMove][toY][toX] == EmptySquare
10311              && fromX != toX && fromY != toY)
10312                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10313         // promotion suffix
10314         if(promoChar != NULLCHAR) {
10315             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10316                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10317                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10318             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10319         }
10320         if(!loadFlag) {
10321                 char buf[MOVE_LEN*2], *p; int len;
10322             fprintf(serverMoves, "/%d/%d",
10323                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10324             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10325             else                      timeLeft = blackTimeRemaining/1000;
10326             fprintf(serverMoves, "/%d", timeLeft);
10327                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10328                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10329                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10330                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10331             fprintf(serverMoves, "/%s", buf);
10332         }
10333         fflush(serverMoves);
10334     }
10335
10336     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10337         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10338       return;
10339     }
10340     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10341     if (commentList[forwardMostMove+1] != NULL) {
10342         free(commentList[forwardMostMove+1]);
10343         commentList[forwardMostMove+1] = NULL;
10344     }
10345     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10346     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10347     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10348     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10349     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10350     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10351     adjustedClock = FALSE;
10352     gameInfo.result = GameUnfinished;
10353     if (gameInfo.resultDetails != NULL) {
10354         free(gameInfo.resultDetails);
10355         gameInfo.resultDetails = NULL;
10356     }
10357     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10358                               moveList[forwardMostMove - 1]);
10359     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10360       case MT_NONE:
10361       case MT_STALEMATE:
10362       default:
10363         break;
10364       case MT_CHECK:
10365         if(!IS_SHOGI(gameInfo.variant))
10366             strcat(parseList[forwardMostMove - 1], "+");
10367         break;
10368       case MT_CHECKMATE:
10369       case MT_STAINMATE:
10370         strcat(parseList[forwardMostMove - 1], "#");
10371         break;
10372     }
10373 }
10374
10375 /* Updates currentMove if not pausing */
10376 void
10377 ShowMove (int fromX, int fromY, int toX, int toY)
10378 {
10379     int instant = (gameMode == PlayFromGameFile) ?
10380         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10381     if(appData.noGUI) return;
10382     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10383         if (!instant) {
10384             if (forwardMostMove == currentMove + 1) {
10385                 AnimateMove(boards[forwardMostMove - 1],
10386                             fromX, fromY, toX, toY);
10387             }
10388         }
10389         currentMove = forwardMostMove;
10390     }
10391
10392     killX = killY = -1; // [HGM] lion: used up
10393
10394     if (instant) return;
10395
10396     DisplayMove(currentMove - 1);
10397     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10398             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10399                 SetHighlights(fromX, fromY, toX, toY);
10400             }
10401     }
10402     DrawPosition(FALSE, boards[currentMove]);
10403     DisplayBothClocks();
10404     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10405 }
10406
10407 void
10408 SendEgtPath (ChessProgramState *cps)
10409 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10410         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10411
10412         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10413
10414         while(*p) {
10415             char c, *q = name+1, *r, *s;
10416
10417             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10418             while(*p && *p != ',') *q++ = *p++;
10419             *q++ = ':'; *q = 0;
10420             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10421                 strcmp(name, ",nalimov:") == 0 ) {
10422                 // take nalimov path from the menu-changeable option first, if it is defined
10423               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10424                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10425             } else
10426             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10427                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10428                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10429                 s = r = StrStr(s, ":") + 1; // beginning of path info
10430                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10431                 c = *r; *r = 0;             // temporarily null-terminate path info
10432                     *--q = 0;               // strip of trailig ':' from name
10433                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10434                 *r = c;
10435                 SendToProgram(buf,cps);     // send egtbpath command for this format
10436             }
10437             if(*p == ',') p++; // read away comma to position for next format name
10438         }
10439 }
10440
10441 static int
10442 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10443 {
10444       int width = 8, height = 8, holdings = 0;             // most common sizes
10445       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10446       // correct the deviations default for each variant
10447       if( v == VariantXiangqi ) width = 9,  height = 10;
10448       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10449       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10450       if( v == VariantCapablanca || v == VariantCapaRandom ||
10451           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10452                                 width = 10;
10453       if( v == VariantCourier ) width = 12;
10454       if( v == VariantSuper )                            holdings = 8;
10455       if( v == VariantGreat )   width = 10,              holdings = 8;
10456       if( v == VariantSChess )                           holdings = 7;
10457       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10458       if( v == VariantChuChess) width = 10, height = 10;
10459       if( v == VariantChu )     width = 12, height = 12;
10460       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10461              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10462              holdingsSize >= 0 && holdingsSize != holdings;
10463 }
10464
10465 char variantError[MSG_SIZ];
10466
10467 char *
10468 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10469 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10470       char *p, *variant = VariantName(v);
10471       static char b[MSG_SIZ];
10472       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10473            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10474                                                holdingsSize, variant); // cook up sized variant name
10475            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10476            if(StrStr(list, b) == NULL) {
10477                // specific sized variant not known, check if general sizing allowed
10478                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10479                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10480                             boardWidth, boardHeight, holdingsSize, engine);
10481                    return NULL;
10482                }
10483                /* [HGM] here we really should compare with the maximum supported board size */
10484            }
10485       } else snprintf(b, MSG_SIZ,"%s", variant);
10486       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10487       p = StrStr(list, b);
10488       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10489       if(p == NULL) {
10490           // occurs not at all in list, or only as sub-string
10491           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10492           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10493               int l = strlen(variantError);
10494               char *q;
10495               while(p != list && p[-1] != ',') p--;
10496               q = strchr(p, ',');
10497               if(q) *q = NULLCHAR;
10498               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10499               if(q) *q= ',';
10500           }
10501           return NULL;
10502       }
10503       return b;
10504 }
10505
10506 void
10507 InitChessProgram (ChessProgramState *cps, int setup)
10508 /* setup needed to setup FRC opening position */
10509 {
10510     char buf[MSG_SIZ], *b;
10511     if (appData.noChessProgram) return;
10512     hintRequested = FALSE;
10513     bookRequested = FALSE;
10514
10515     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10516     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10517     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10518     if(cps->memSize) { /* [HGM] memory */
10519       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10520         SendToProgram(buf, cps);
10521     }
10522     SendEgtPath(cps); /* [HGM] EGT */
10523     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10524       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10525         SendToProgram(buf, cps);
10526     }
10527
10528     setboardSpoiledMachineBlack = FALSE;
10529     SendToProgram(cps->initString, cps);
10530     if (gameInfo.variant != VariantNormal &&
10531         gameInfo.variant != VariantLoadable
10532         /* [HGM] also send variant if board size non-standard */
10533         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10534
10535       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10536                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10537       if (b == NULL) {
10538         VariantClass v;
10539         char c, *q = cps->variants, *p = strchr(q, ',');
10540         if(p) *p = NULLCHAR;
10541         v = StringToVariant(q);
10542         DisplayError(variantError, 0);
10543         if(v != VariantUnknown && cps == &first) {
10544             int w, h, s;
10545             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10546                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10547             ASSIGN(appData.variant, q);
10548             Reset(TRUE, FALSE);
10549         }
10550         if(p) *p = ',';
10551         return;
10552       }
10553
10554       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10555       SendToProgram(buf, cps);
10556     }
10557     currentlyInitializedVariant = gameInfo.variant;
10558
10559     /* [HGM] send opening position in FRC to first engine */
10560     if(setup) {
10561           SendToProgram("force\n", cps);
10562           SendBoard(cps, 0);
10563           /* engine is now in force mode! Set flag to wake it up after first move. */
10564           setboardSpoiledMachineBlack = 1;
10565     }
10566
10567     if (cps->sendICS) {
10568       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10569       SendToProgram(buf, cps);
10570     }
10571     cps->maybeThinking = FALSE;
10572     cps->offeredDraw = 0;
10573     if (!appData.icsActive) {
10574         SendTimeControl(cps, movesPerSession, timeControl,
10575                         timeIncrement, appData.searchDepth,
10576                         searchTime);
10577     }
10578     if (appData.showThinking
10579         // [HGM] thinking: four options require thinking output to be sent
10580         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10581                                 ) {
10582         SendToProgram("post\n", cps);
10583     }
10584     SendToProgram("hard\n", cps);
10585     if (!appData.ponderNextMove) {
10586         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10587            it without being sure what state we are in first.  "hard"
10588            is not a toggle, so that one is OK.
10589          */
10590         SendToProgram("easy\n", cps);
10591     }
10592     if (cps->usePing) {
10593       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10594       SendToProgram(buf, cps);
10595     }
10596     cps->initDone = TRUE;
10597     ClearEngineOutputPane(cps == &second);
10598 }
10599
10600
10601 void
10602 ResendOptions (ChessProgramState *cps)
10603 { // send the stored value of the options
10604   int i;
10605   char buf[MSG_SIZ];
10606   Option *opt = cps->option;
10607   for(i=0; i<cps->nrOptions; i++, opt++) {
10608       switch(opt->type) {
10609         case Spin:
10610         case Slider:
10611         case CheckBox:
10612             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10613           break;
10614         case ComboBox:
10615           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10616           break;
10617         default:
10618             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10619           break;
10620         case Button:
10621         case SaveButton:
10622           continue;
10623       }
10624       SendToProgram(buf, cps);
10625   }
10626 }
10627
10628 void
10629 StartChessProgram (ChessProgramState *cps)
10630 {
10631     char buf[MSG_SIZ];
10632     int err;
10633
10634     if (appData.noChessProgram) return;
10635     cps->initDone = FALSE;
10636
10637     if (strcmp(cps->host, "localhost") == 0) {
10638         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10639     } else if (*appData.remoteShell == NULLCHAR) {
10640         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10641     } else {
10642         if (*appData.remoteUser == NULLCHAR) {
10643           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10644                     cps->program);
10645         } else {
10646           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10647                     cps->host, appData.remoteUser, cps->program);
10648         }
10649         err = StartChildProcess(buf, "", &cps->pr);
10650     }
10651
10652     if (err != 0) {
10653       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10654         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10655         if(cps != &first) return;
10656         appData.noChessProgram = TRUE;
10657         ThawUI();
10658         SetNCPMode();
10659 //      DisplayFatalError(buf, err, 1);
10660 //      cps->pr = NoProc;
10661 //      cps->isr = NULL;
10662         return;
10663     }
10664
10665     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10666     if (cps->protocolVersion > 1) {
10667       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10668       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10669         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10670         cps->comboCnt = 0;  //                and values of combo boxes
10671       }
10672       SendToProgram(buf, cps);
10673       if(cps->reload) ResendOptions(cps);
10674     } else {
10675       SendToProgram("xboard\n", cps);
10676     }
10677 }
10678
10679 void
10680 TwoMachinesEventIfReady P((void))
10681 {
10682   static int curMess = 0;
10683   if (first.lastPing != first.lastPong) {
10684     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10685     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10686     return;
10687   }
10688   if (second.lastPing != second.lastPong) {
10689     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10690     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10691     return;
10692   }
10693   DisplayMessage("", ""); curMess = 0;
10694   TwoMachinesEvent();
10695 }
10696
10697 char *
10698 MakeName (char *template)
10699 {
10700     time_t clock;
10701     struct tm *tm;
10702     static char buf[MSG_SIZ];
10703     char *p = buf;
10704     int i;
10705
10706     clock = time((time_t *)NULL);
10707     tm = localtime(&clock);
10708
10709     while(*p++ = *template++) if(p[-1] == '%') {
10710         switch(*template++) {
10711           case 0:   *p = 0; return buf;
10712           case 'Y': i = tm->tm_year+1900; break;
10713           case 'y': i = tm->tm_year-100; break;
10714           case 'M': i = tm->tm_mon+1; break;
10715           case 'd': i = tm->tm_mday; break;
10716           case 'h': i = tm->tm_hour; break;
10717           case 'm': i = tm->tm_min; break;
10718           case 's': i = tm->tm_sec; break;
10719           default:  i = 0;
10720         }
10721         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10722     }
10723     return buf;
10724 }
10725
10726 int
10727 CountPlayers (char *p)
10728 {
10729     int n = 0;
10730     while(p = strchr(p, '\n')) p++, n++; // count participants
10731     return n;
10732 }
10733
10734 FILE *
10735 WriteTourneyFile (char *results, FILE *f)
10736 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10737     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10738     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10739         // create a file with tournament description
10740         fprintf(f, "-participants {%s}\n", appData.participants);
10741         fprintf(f, "-seedBase %d\n", appData.seedBase);
10742         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10743         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10744         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10745         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10746         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10747         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10748         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10749         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10750         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10751         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10752         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10753         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10754         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10755         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10756         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10757         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10758         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10759         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10760         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10761         fprintf(f, "-smpCores %d\n", appData.smpCores);
10762         if(searchTime > 0)
10763                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10764         else {
10765                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10766                 fprintf(f, "-tc %s\n", appData.timeControl);
10767                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10768         }
10769         fprintf(f, "-results \"%s\"\n", results);
10770     }
10771     return f;
10772 }
10773
10774 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10775
10776 void
10777 Substitute (char *participants, int expunge)
10778 {
10779     int i, changed, changes=0, nPlayers=0;
10780     char *p, *q, *r, buf[MSG_SIZ];
10781     if(participants == NULL) return;
10782     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10783     r = p = participants; q = appData.participants;
10784     while(*p && *p == *q) {
10785         if(*p == '\n') r = p+1, nPlayers++;
10786         p++; q++;
10787     }
10788     if(*p) { // difference
10789         while(*p && *p++ != '\n');
10790         while(*q && *q++ != '\n');
10791       changed = nPlayers;
10792         changes = 1 + (strcmp(p, q) != 0);
10793     }
10794     if(changes == 1) { // a single engine mnemonic was changed
10795         q = r; while(*q) nPlayers += (*q++ == '\n');
10796         p = buf; while(*r && (*p = *r++) != '\n') p++;
10797         *p = NULLCHAR;
10798         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10799         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10800         if(mnemonic[i]) { // The substitute is valid
10801             FILE *f;
10802             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10803                 flock(fileno(f), LOCK_EX);
10804                 ParseArgsFromFile(f);
10805                 fseek(f, 0, SEEK_SET);
10806                 FREE(appData.participants); appData.participants = participants;
10807                 if(expunge) { // erase results of replaced engine
10808                     int len = strlen(appData.results), w, b, dummy;
10809                     for(i=0; i<len; i++) {
10810                         Pairing(i, nPlayers, &w, &b, &dummy);
10811                         if((w == changed || b == changed) && appData.results[i] == '*') {
10812                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10813                             fclose(f);
10814                             return;
10815                         }
10816                     }
10817                     for(i=0; i<len; i++) {
10818                         Pairing(i, nPlayers, &w, &b, &dummy);
10819                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10820                     }
10821                 }
10822                 WriteTourneyFile(appData.results, f);
10823                 fclose(f); // release lock
10824                 return;
10825             }
10826         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10827     }
10828     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10829     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10830     free(participants);
10831     return;
10832 }
10833
10834 int
10835 CheckPlayers (char *participants)
10836 {
10837         int i;
10838         char buf[MSG_SIZ], *p;
10839         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10840         while(p = strchr(participants, '\n')) {
10841             *p = NULLCHAR;
10842             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10843             if(!mnemonic[i]) {
10844                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10845                 *p = '\n';
10846                 DisplayError(buf, 0);
10847                 return 1;
10848             }
10849             *p = '\n';
10850             participants = p + 1;
10851         }
10852         return 0;
10853 }
10854
10855 int
10856 CreateTourney (char *name)
10857 {
10858         FILE *f;
10859         if(matchMode && strcmp(name, appData.tourneyFile)) {
10860              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10861         }
10862         if(name[0] == NULLCHAR) {
10863             if(appData.participants[0])
10864                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10865             return 0;
10866         }
10867         f = fopen(name, "r");
10868         if(f) { // file exists
10869             ASSIGN(appData.tourneyFile, name);
10870             ParseArgsFromFile(f); // parse it
10871         } else {
10872             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10873             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10874                 DisplayError(_("Not enough participants"), 0);
10875                 return 0;
10876             }
10877             if(CheckPlayers(appData.participants)) return 0;
10878             ASSIGN(appData.tourneyFile, name);
10879             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10880             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10881         }
10882         fclose(f);
10883         appData.noChessProgram = FALSE;
10884         appData.clockMode = TRUE;
10885         SetGNUMode();
10886         return 1;
10887 }
10888
10889 int
10890 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10891 {
10892     char buf[MSG_SIZ], *p, *q;
10893     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10894     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10895     skip = !all && group[0]; // if group requested, we start in skip mode
10896     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10897         p = names; q = buf; header = 0;
10898         while(*p && *p != '\n') *q++ = *p++;
10899         *q = 0;
10900         if(*p == '\n') p++;
10901         if(buf[0] == '#') {
10902             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10903             depth++; // we must be entering a new group
10904             if(all) continue; // suppress printing group headers when complete list requested
10905             header = 1;
10906             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10907         }
10908         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10909         if(engineList[i]) free(engineList[i]);
10910         engineList[i] = strdup(buf);
10911         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10912         if(engineMnemonic[i]) free(engineMnemonic[i]);
10913         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10914             strcat(buf, " (");
10915             sscanf(q + 8, "%s", buf + strlen(buf));
10916             strcat(buf, ")");
10917         }
10918         engineMnemonic[i] = strdup(buf);
10919         i++;
10920     }
10921     engineList[i] = engineMnemonic[i] = NULL;
10922     return i;
10923 }
10924
10925 // following implemented as macro to avoid type limitations
10926 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10927
10928 void
10929 SwapEngines (int n)
10930 {   // swap settings for first engine and other engine (so far only some selected options)
10931     int h;
10932     char *p;
10933     if(n == 0) return;
10934     SWAP(directory, p)
10935     SWAP(chessProgram, p)
10936     SWAP(isUCI, h)
10937     SWAP(hasOwnBookUCI, h)
10938     SWAP(protocolVersion, h)
10939     SWAP(reuse, h)
10940     SWAP(scoreIsAbsolute, h)
10941     SWAP(timeOdds, h)
10942     SWAP(logo, p)
10943     SWAP(pgnName, p)
10944     SWAP(pvSAN, h)
10945     SWAP(engOptions, p)
10946     SWAP(engInitString, p)
10947     SWAP(computerString, p)
10948     SWAP(features, p)
10949     SWAP(fenOverride, p)
10950     SWAP(NPS, h)
10951     SWAP(accumulateTC, h)
10952     SWAP(drawDepth, h)
10953     SWAP(host, p)
10954     SWAP(pseudo, h)
10955 }
10956
10957 int
10958 GetEngineLine (char *s, int n)
10959 {
10960     int i;
10961     char buf[MSG_SIZ];
10962     extern char *icsNames;
10963     if(!s || !*s) return 0;
10964     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10965     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10966     if(!mnemonic[i]) return 0;
10967     if(n == 11) return 1; // just testing if there was a match
10968     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10969     if(n == 1) SwapEngines(n);
10970     ParseArgsFromString(buf);
10971     if(n == 1) SwapEngines(n);
10972     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10973         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10974         ParseArgsFromString(buf);
10975     }
10976     return 1;
10977 }
10978
10979 int
10980 SetPlayer (int player, char *p)
10981 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10982     int i;
10983     char buf[MSG_SIZ], *engineName;
10984     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10985     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10986     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10987     if(mnemonic[i]) {
10988         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10989         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10990         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10991         ParseArgsFromString(buf);
10992     } else { // no engine with this nickname is installed!
10993         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10994         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10995         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10996         ModeHighlight();
10997         DisplayError(buf, 0);
10998         return 0;
10999     }
11000     free(engineName);
11001     return i;
11002 }
11003
11004 char *recentEngines;
11005
11006 void
11007 RecentEngineEvent (int nr)
11008 {
11009     int n;
11010 //    SwapEngines(1); // bump first to second
11011 //    ReplaceEngine(&second, 1); // and load it there
11012     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11013     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11014     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11015         ReplaceEngine(&first, 0);
11016         FloatToFront(&appData.recentEngineList, command[n]);
11017     }
11018 }
11019
11020 int
11021 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11022 {   // determine players from game number
11023     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11024
11025     if(appData.tourneyType == 0) {
11026         roundsPerCycle = (nPlayers - 1) | 1;
11027         pairingsPerRound = nPlayers / 2;
11028     } else if(appData.tourneyType > 0) {
11029         roundsPerCycle = nPlayers - appData.tourneyType;
11030         pairingsPerRound = appData.tourneyType;
11031     }
11032     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11033     gamesPerCycle = gamesPerRound * roundsPerCycle;
11034     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11035     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11036     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11037     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11038     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11039     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11040
11041     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11042     if(appData.roundSync) *syncInterval = gamesPerRound;
11043
11044     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11045
11046     if(appData.tourneyType == 0) {
11047         if(curPairing == (nPlayers-1)/2 ) {
11048             *whitePlayer = curRound;
11049             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11050         } else {
11051             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11052             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11053             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11054             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11055         }
11056     } else if(appData.tourneyType > 1) {
11057         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11058         *whitePlayer = curRound + appData.tourneyType;
11059     } else if(appData.tourneyType > 0) {
11060         *whitePlayer = curPairing;
11061         *blackPlayer = curRound + appData.tourneyType;
11062     }
11063
11064     // take care of white/black alternation per round.
11065     // For cycles and games this is already taken care of by default, derived from matchGame!
11066     return curRound & 1;
11067 }
11068
11069 int
11070 NextTourneyGame (int nr, int *swapColors)
11071 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11072     char *p, *q;
11073     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11074     FILE *tf;
11075     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11076     tf = fopen(appData.tourneyFile, "r");
11077     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11078     ParseArgsFromFile(tf); fclose(tf);
11079     InitTimeControls(); // TC might be altered from tourney file
11080
11081     nPlayers = CountPlayers(appData.participants); // count participants
11082     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11083     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11084
11085     if(syncInterval) {
11086         p = q = appData.results;
11087         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11088         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11089             DisplayMessage(_("Waiting for other game(s)"),"");
11090             waitingForGame = TRUE;
11091             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11092             return 0;
11093         }
11094         waitingForGame = FALSE;
11095     }
11096
11097     if(appData.tourneyType < 0) {
11098         if(nr>=0 && !pairingReceived) {
11099             char buf[1<<16];
11100             if(pairing.pr == NoProc) {
11101                 if(!appData.pairingEngine[0]) {
11102                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11103                     return 0;
11104                 }
11105                 StartChessProgram(&pairing); // starts the pairing engine
11106             }
11107             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11108             SendToProgram(buf, &pairing);
11109             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11110             SendToProgram(buf, &pairing);
11111             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11112         }
11113         pairingReceived = 0;                              // ... so we continue here
11114         *swapColors = 0;
11115         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11116         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11117         matchGame = 1; roundNr = nr / syncInterval + 1;
11118     }
11119
11120     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11121
11122     // redefine engines, engine dir, etc.
11123     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11124     if(first.pr == NoProc) {
11125       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11126       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11127     }
11128     if(second.pr == NoProc) {
11129       SwapEngines(1);
11130       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11131       SwapEngines(1);         // and make that valid for second engine by swapping
11132       InitEngine(&second, 1);
11133     }
11134     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11135     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11136     return OK;
11137 }
11138
11139 void
11140 NextMatchGame ()
11141 {   // performs game initialization that does not invoke engines, and then tries to start the game
11142     int res, firstWhite, swapColors = 0;
11143     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11144     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
11145         char buf[MSG_SIZ];
11146         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11147         if(strcmp(buf, currentDebugFile)) { // name has changed
11148             FILE *f = fopen(buf, "w");
11149             if(f) { // if opening the new file failed, just keep using the old one
11150                 ASSIGN(currentDebugFile, buf);
11151                 fclose(debugFP);
11152                 debugFP = f;
11153             }
11154             if(appData.serverFileName) {
11155                 if(serverFP) fclose(serverFP);
11156                 serverFP = fopen(appData.serverFileName, "w");
11157                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11158                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11159             }
11160         }
11161     }
11162     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11163     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11164     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11165     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11166     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11167     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11168     Reset(FALSE, first.pr != NoProc);
11169     res = LoadGameOrPosition(matchGame); // setup game
11170     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11171     if(!res) return; // abort when bad game/pos file
11172     TwoMachinesEvent();
11173 }
11174
11175 void
11176 UserAdjudicationEvent (int result)
11177 {
11178     ChessMove gameResult = GameIsDrawn;
11179
11180     if( result > 0 ) {
11181         gameResult = WhiteWins;
11182     }
11183     else if( result < 0 ) {
11184         gameResult = BlackWins;
11185     }
11186
11187     if( gameMode == TwoMachinesPlay ) {
11188         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11189     }
11190 }
11191
11192
11193 // [HGM] save: calculate checksum of game to make games easily identifiable
11194 int
11195 StringCheckSum (char *s)
11196 {
11197         int i = 0;
11198         if(s==NULL) return 0;
11199         while(*s) i = i*259 + *s++;
11200         return i;
11201 }
11202
11203 int
11204 GameCheckSum ()
11205 {
11206         int i, sum=0;
11207         for(i=backwardMostMove; i<forwardMostMove; i++) {
11208                 sum += pvInfoList[i].depth;
11209                 sum += StringCheckSum(parseList[i]);
11210                 sum += StringCheckSum(commentList[i]);
11211                 sum *= 261;
11212         }
11213         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11214         return sum + StringCheckSum(commentList[i]);
11215 } // end of save patch
11216
11217 void
11218 GameEnds (ChessMove result, char *resultDetails, int whosays)
11219 {
11220     GameMode nextGameMode;
11221     int isIcsGame;
11222     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11223
11224     if(endingGame) return; /* [HGM] crash: forbid recursion */
11225     endingGame = 1;
11226     if(twoBoards) { // [HGM] dual: switch back to one board
11227         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11228         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11229     }
11230     if (appData.debugMode) {
11231       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11232               result, resultDetails ? resultDetails : "(null)", whosays);
11233     }
11234
11235     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11236
11237     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11238
11239     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11240         /* If we are playing on ICS, the server decides when the
11241            game is over, but the engine can offer to draw, claim
11242            a draw, or resign.
11243          */
11244 #if ZIPPY
11245         if (appData.zippyPlay && first.initDone) {
11246             if (result == GameIsDrawn) {
11247                 /* In case draw still needs to be claimed */
11248                 SendToICS(ics_prefix);
11249                 SendToICS("draw\n");
11250             } else if (StrCaseStr(resultDetails, "resign")) {
11251                 SendToICS(ics_prefix);
11252                 SendToICS("resign\n");
11253             }
11254         }
11255 #endif
11256         endingGame = 0; /* [HGM] crash */
11257         return;
11258     }
11259
11260     /* If we're loading the game from a file, stop */
11261     if (whosays == GE_FILE) {
11262       (void) StopLoadGameTimer();
11263       gameFileFP = NULL;
11264     }
11265
11266     /* Cancel draw offers */
11267     first.offeredDraw = second.offeredDraw = 0;
11268
11269     /* If this is an ICS game, only ICS can really say it's done;
11270        if not, anyone can. */
11271     isIcsGame = (gameMode == IcsPlayingWhite ||
11272                  gameMode == IcsPlayingBlack ||
11273                  gameMode == IcsObserving    ||
11274                  gameMode == IcsExamining);
11275
11276     if (!isIcsGame || whosays == GE_ICS) {
11277         /* OK -- not an ICS game, or ICS said it was done */
11278         StopClocks();
11279         if (!isIcsGame && !appData.noChessProgram)
11280           SetUserThinkingEnables();
11281
11282         /* [HGM] if a machine claims the game end we verify this claim */
11283         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11284             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11285                 char claimer;
11286                 ChessMove trueResult = (ChessMove) -1;
11287
11288                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11289                                             first.twoMachinesColor[0] :
11290                                             second.twoMachinesColor[0] ;
11291
11292                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11293                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11294                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11295                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11296                 } else
11297                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11298                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11299                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11300                 } else
11301                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11302                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11303                 }
11304
11305                 // now verify win claims, but not in drop games, as we don't understand those yet
11306                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11307                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11308                     (result == WhiteWins && claimer == 'w' ||
11309                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11310                       if (appData.debugMode) {
11311                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11312                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11313                       }
11314                       if(result != trueResult) {
11315                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11316                               result = claimer == 'w' ? BlackWins : WhiteWins;
11317                               resultDetails = buf;
11318                       }
11319                 } else
11320                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11321                     && (forwardMostMove <= backwardMostMove ||
11322                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11323                         (claimer=='b')==(forwardMostMove&1))
11324                                                                                   ) {
11325                       /* [HGM] verify: draws that were not flagged are false claims */
11326                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11327                       result = claimer == 'w' ? BlackWins : WhiteWins;
11328                       resultDetails = buf;
11329                 }
11330                 /* (Claiming a loss is accepted no questions asked!) */
11331             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11332                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11333                 result = GameUnfinished;
11334                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11335             }
11336             /* [HGM] bare: don't allow bare King to win */
11337             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11338                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11339                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11340                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11341                && result != GameIsDrawn)
11342             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11343                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11344                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11345                         if(p >= 0 && p <= (int)WhiteKing) k++;
11346                 }
11347                 if (appData.debugMode) {
11348                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11349                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11350                 }
11351                 if(k <= 1) {
11352                         result = GameIsDrawn;
11353                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11354                         resultDetails = buf;
11355                 }
11356             }
11357         }
11358
11359
11360         if(serverMoves != NULL && !loadFlag) { char c = '=';
11361             if(result==WhiteWins) c = '+';
11362             if(result==BlackWins) c = '-';
11363             if(resultDetails != NULL)
11364                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11365         }
11366         if (resultDetails != NULL) {
11367             gameInfo.result = result;
11368             gameInfo.resultDetails = StrSave(resultDetails);
11369
11370             /* display last move only if game was not loaded from file */
11371             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11372                 DisplayMove(currentMove - 1);
11373
11374             if (forwardMostMove != 0) {
11375                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11376                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11377                                                                 ) {
11378                     if (*appData.saveGameFile != NULLCHAR) {
11379                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11380                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11381                         else
11382                         SaveGameToFile(appData.saveGameFile, TRUE);
11383                     } else if (appData.autoSaveGames) {
11384                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11385                     }
11386                     if (*appData.savePositionFile != NULLCHAR) {
11387                         SavePositionToFile(appData.savePositionFile);
11388                     }
11389                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11390                 }
11391             }
11392
11393             /* Tell program how game ended in case it is learning */
11394             /* [HGM] Moved this to after saving the PGN, just in case */
11395             /* engine died and we got here through time loss. In that */
11396             /* case we will get a fatal error writing the pipe, which */
11397             /* would otherwise lose us the PGN.                       */
11398             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11399             /* output during GameEnds should never be fatal anymore   */
11400             if (gameMode == MachinePlaysWhite ||
11401                 gameMode == MachinePlaysBlack ||
11402                 gameMode == TwoMachinesPlay ||
11403                 gameMode == IcsPlayingWhite ||
11404                 gameMode == IcsPlayingBlack ||
11405                 gameMode == BeginningOfGame) {
11406                 char buf[MSG_SIZ];
11407                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11408                         resultDetails);
11409                 if (first.pr != NoProc) {
11410                     SendToProgram(buf, &first);
11411                 }
11412                 if (second.pr != NoProc &&
11413                     gameMode == TwoMachinesPlay) {
11414                     SendToProgram(buf, &second);
11415                 }
11416             }
11417         }
11418
11419         if (appData.icsActive) {
11420             if (appData.quietPlay &&
11421                 (gameMode == IcsPlayingWhite ||
11422                  gameMode == IcsPlayingBlack)) {
11423                 SendToICS(ics_prefix);
11424                 SendToICS("set shout 1\n");
11425             }
11426             nextGameMode = IcsIdle;
11427             ics_user_moved = FALSE;
11428             /* clean up premove.  It's ugly when the game has ended and the
11429              * premove highlights are still on the board.
11430              */
11431             if (gotPremove) {
11432               gotPremove = FALSE;
11433               ClearPremoveHighlights();
11434               DrawPosition(FALSE, boards[currentMove]);
11435             }
11436             if (whosays == GE_ICS) {
11437                 switch (result) {
11438                 case WhiteWins:
11439                     if (gameMode == IcsPlayingWhite)
11440                         PlayIcsWinSound();
11441                     else if(gameMode == IcsPlayingBlack)
11442                         PlayIcsLossSound();
11443                     break;
11444                 case BlackWins:
11445                     if (gameMode == IcsPlayingBlack)
11446                         PlayIcsWinSound();
11447                     else if(gameMode == IcsPlayingWhite)
11448                         PlayIcsLossSound();
11449                     break;
11450                 case GameIsDrawn:
11451                     PlayIcsDrawSound();
11452                     break;
11453                 default:
11454                     PlayIcsUnfinishedSound();
11455                 }
11456             }
11457             if(appData.quitNext) { ExitEvent(0); return; }
11458         } else if (gameMode == EditGame ||
11459                    gameMode == PlayFromGameFile ||
11460                    gameMode == AnalyzeMode ||
11461                    gameMode == AnalyzeFile) {
11462             nextGameMode = gameMode;
11463         } else {
11464             nextGameMode = EndOfGame;
11465         }
11466         pausing = FALSE;
11467         ModeHighlight();
11468     } else {
11469         nextGameMode = gameMode;
11470     }
11471
11472     if (appData.noChessProgram) {
11473         gameMode = nextGameMode;
11474         ModeHighlight();
11475         endingGame = 0; /* [HGM] crash */
11476         return;
11477     }
11478
11479     if (first.reuse) {
11480         /* Put first chess program into idle state */
11481         if (first.pr != NoProc &&
11482             (gameMode == MachinePlaysWhite ||
11483              gameMode == MachinePlaysBlack ||
11484              gameMode == TwoMachinesPlay ||
11485              gameMode == IcsPlayingWhite ||
11486              gameMode == IcsPlayingBlack ||
11487              gameMode == BeginningOfGame)) {
11488             SendToProgram("force\n", &first);
11489             if (first.usePing) {
11490               char buf[MSG_SIZ];
11491               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11492               SendToProgram(buf, &first);
11493             }
11494         }
11495     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11496         /* Kill off first chess program */
11497         if (first.isr != NULL)
11498           RemoveInputSource(first.isr);
11499         first.isr = NULL;
11500
11501         if (first.pr != NoProc) {
11502             ExitAnalyzeMode();
11503             DoSleep( appData.delayBeforeQuit );
11504             SendToProgram("quit\n", &first);
11505             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11506             first.reload = TRUE;
11507         }
11508         first.pr = NoProc;
11509     }
11510     if (second.reuse) {
11511         /* Put second chess program into idle state */
11512         if (second.pr != NoProc &&
11513             gameMode == TwoMachinesPlay) {
11514             SendToProgram("force\n", &second);
11515             if (second.usePing) {
11516               char buf[MSG_SIZ];
11517               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11518               SendToProgram(buf, &second);
11519             }
11520         }
11521     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11522         /* Kill off second chess program */
11523         if (second.isr != NULL)
11524           RemoveInputSource(second.isr);
11525         second.isr = NULL;
11526
11527         if (second.pr != NoProc) {
11528             DoSleep( appData.delayBeforeQuit );
11529             SendToProgram("quit\n", &second);
11530             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11531             second.reload = TRUE;
11532         }
11533         second.pr = NoProc;
11534     }
11535
11536     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11537         char resChar = '=';
11538         switch (result) {
11539         case WhiteWins:
11540           resChar = '+';
11541           if (first.twoMachinesColor[0] == 'w') {
11542             first.matchWins++;
11543           } else {
11544             second.matchWins++;
11545           }
11546           break;
11547         case BlackWins:
11548           resChar = '-';
11549           if (first.twoMachinesColor[0] == 'b') {
11550             first.matchWins++;
11551           } else {
11552             second.matchWins++;
11553           }
11554           break;
11555         case GameUnfinished:
11556           resChar = ' ';
11557         default:
11558           break;
11559         }
11560
11561         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11562         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11563             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11564             ReserveGame(nextGame, resChar); // sets nextGame
11565             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11566             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11567         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11568
11569         if (nextGame <= appData.matchGames && !abortMatch) {
11570             gameMode = nextGameMode;
11571             matchGame = nextGame; // this will be overruled in tourney mode!
11572             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11573             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11574             endingGame = 0; /* [HGM] crash */
11575             return;
11576         } else {
11577             gameMode = nextGameMode;
11578             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11579                      first.tidy, second.tidy,
11580                      first.matchWins, second.matchWins,
11581                      appData.matchGames - (first.matchWins + second.matchWins));
11582             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11583             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11584             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11585             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11586                 first.twoMachinesColor = "black\n";
11587                 second.twoMachinesColor = "white\n";
11588             } else {
11589                 first.twoMachinesColor = "white\n";
11590                 second.twoMachinesColor = "black\n";
11591             }
11592         }
11593     }
11594     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11595         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11596       ExitAnalyzeMode();
11597     gameMode = nextGameMode;
11598     ModeHighlight();
11599     endingGame = 0;  /* [HGM] crash */
11600     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11601         if(matchMode == TRUE) { // match through command line: exit with or without popup
11602             if(ranking) {
11603                 ToNrEvent(forwardMostMove);
11604                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11605                 else ExitEvent(0);
11606             } else DisplayFatalError(buf, 0, 0);
11607         } else { // match through menu; just stop, with or without popup
11608             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11609             ModeHighlight();
11610             if(ranking){
11611                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11612             } else DisplayNote(buf);
11613       }
11614       if(ranking) free(ranking);
11615     }
11616 }
11617
11618 /* Assumes program was just initialized (initString sent).
11619    Leaves program in force mode. */
11620 void
11621 FeedMovesToProgram (ChessProgramState *cps, int upto)
11622 {
11623     int i;
11624
11625     if (appData.debugMode)
11626       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11627               startedFromSetupPosition ? "position and " : "",
11628               backwardMostMove, upto, cps->which);
11629     if(currentlyInitializedVariant != gameInfo.variant) {
11630       char buf[MSG_SIZ];
11631         // [HGM] variantswitch: make engine aware of new variant
11632         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11633                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11634                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11635         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11636         SendToProgram(buf, cps);
11637         currentlyInitializedVariant = gameInfo.variant;
11638     }
11639     SendToProgram("force\n", cps);
11640     if (startedFromSetupPosition) {
11641         SendBoard(cps, backwardMostMove);
11642     if (appData.debugMode) {
11643         fprintf(debugFP, "feedMoves\n");
11644     }
11645     }
11646     for (i = backwardMostMove; i < upto; i++) {
11647         SendMoveToProgram(i, cps);
11648     }
11649 }
11650
11651
11652 int
11653 ResurrectChessProgram ()
11654 {
11655      /* The chess program may have exited.
11656         If so, restart it and feed it all the moves made so far. */
11657     static int doInit = 0;
11658
11659     if (appData.noChessProgram) return 1;
11660
11661     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11662         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11663         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11664         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11665     } else {
11666         if (first.pr != NoProc) return 1;
11667         StartChessProgram(&first);
11668     }
11669     InitChessProgram(&first, FALSE);
11670     FeedMovesToProgram(&first, currentMove);
11671
11672     if (!first.sendTime) {
11673         /* can't tell gnuchess what its clock should read,
11674            so we bow to its notion. */
11675         ResetClocks();
11676         timeRemaining[0][currentMove] = whiteTimeRemaining;
11677         timeRemaining[1][currentMove] = blackTimeRemaining;
11678     }
11679
11680     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11681                 appData.icsEngineAnalyze) && first.analysisSupport) {
11682       SendToProgram("analyze\n", &first);
11683       first.analyzing = TRUE;
11684     }
11685     return 1;
11686 }
11687
11688 /*
11689  * Button procedures
11690  */
11691 void
11692 Reset (int redraw, int init)
11693 {
11694     int i;
11695
11696     if (appData.debugMode) {
11697         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11698                 redraw, init, gameMode);
11699     }
11700     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11701     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11702     CleanupTail(); // [HGM] vari: delete any stored variations
11703     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11704     pausing = pauseExamInvalid = FALSE;
11705     startedFromSetupPosition = blackPlaysFirst = FALSE;
11706     firstMove = TRUE;
11707     whiteFlag = blackFlag = FALSE;
11708     userOfferedDraw = FALSE;
11709     hintRequested = bookRequested = FALSE;
11710     first.maybeThinking = FALSE;
11711     second.maybeThinking = FALSE;
11712     first.bookSuspend = FALSE; // [HGM] book
11713     second.bookSuspend = FALSE;
11714     thinkOutput[0] = NULLCHAR;
11715     lastHint[0] = NULLCHAR;
11716     ClearGameInfo(&gameInfo);
11717     gameInfo.variant = StringToVariant(appData.variant);
11718     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11719     ics_user_moved = ics_clock_paused = FALSE;
11720     ics_getting_history = H_FALSE;
11721     ics_gamenum = -1;
11722     white_holding[0] = black_holding[0] = NULLCHAR;
11723     ClearProgramStats();
11724     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11725
11726     ResetFrontEnd();
11727     ClearHighlights();
11728     flipView = appData.flipView;
11729     ClearPremoveHighlights();
11730     gotPremove = FALSE;
11731     alarmSounded = FALSE;
11732     killX = killY = -1; // [HGM] lion
11733
11734     GameEnds(EndOfFile, NULL, GE_PLAYER);
11735     if(appData.serverMovesName != NULL) {
11736         /* [HGM] prepare to make moves file for broadcasting */
11737         clock_t t = clock();
11738         if(serverMoves != NULL) fclose(serverMoves);
11739         serverMoves = fopen(appData.serverMovesName, "r");
11740         if(serverMoves != NULL) {
11741             fclose(serverMoves);
11742             /* delay 15 sec before overwriting, so all clients can see end */
11743             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11744         }
11745         serverMoves = fopen(appData.serverMovesName, "w");
11746     }
11747
11748     ExitAnalyzeMode();
11749     gameMode = BeginningOfGame;
11750     ModeHighlight();
11751     if(appData.icsActive) gameInfo.variant = VariantNormal;
11752     currentMove = forwardMostMove = backwardMostMove = 0;
11753     MarkTargetSquares(1);
11754     InitPosition(redraw);
11755     for (i = 0; i < MAX_MOVES; i++) {
11756         if (commentList[i] != NULL) {
11757             free(commentList[i]);
11758             commentList[i] = NULL;
11759         }
11760     }
11761     ResetClocks();
11762     timeRemaining[0][0] = whiteTimeRemaining;
11763     timeRemaining[1][0] = blackTimeRemaining;
11764
11765     if (first.pr == NoProc) {
11766         StartChessProgram(&first);
11767     }
11768     if (init) {
11769             InitChessProgram(&first, startedFromSetupPosition);
11770     }
11771     DisplayTitle("");
11772     DisplayMessage("", "");
11773     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11774     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11775     ClearMap();        // [HGM] exclude: invalidate map
11776 }
11777
11778 void
11779 AutoPlayGameLoop ()
11780 {
11781     for (;;) {
11782         if (!AutoPlayOneMove())
11783           return;
11784         if (matchMode || appData.timeDelay == 0)
11785           continue;
11786         if (appData.timeDelay < 0)
11787           return;
11788         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11789         break;
11790     }
11791 }
11792
11793 void
11794 AnalyzeNextGame()
11795 {
11796     ReloadGame(1); // next game
11797 }
11798
11799 int
11800 AutoPlayOneMove ()
11801 {
11802     int fromX, fromY, toX, toY;
11803
11804     if (appData.debugMode) {
11805       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11806     }
11807
11808     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11809       return FALSE;
11810
11811     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11812       pvInfoList[currentMove].depth = programStats.depth;
11813       pvInfoList[currentMove].score = programStats.score;
11814       pvInfoList[currentMove].time  = 0;
11815       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11816       else { // append analysis of final position as comment
11817         char buf[MSG_SIZ];
11818         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11819         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11820       }
11821       programStats.depth = 0;
11822     }
11823
11824     if (currentMove >= forwardMostMove) {
11825       if(gameMode == AnalyzeFile) {
11826           if(appData.loadGameIndex == -1) {
11827             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11828           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11829           } else {
11830           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11831         }
11832       }
11833 //      gameMode = EndOfGame;
11834 //      ModeHighlight();
11835
11836       /* [AS] Clear current move marker at the end of a game */
11837       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11838
11839       return FALSE;
11840     }
11841
11842     toX = moveList[currentMove][2] - AAA;
11843     toY = moveList[currentMove][3] - ONE;
11844
11845     if (moveList[currentMove][1] == '@') {
11846         if (appData.highlightLastMove) {
11847             SetHighlights(-1, -1, toX, toY);
11848         }
11849     } else {
11850         int viaX = moveList[currentMove][5] - AAA;
11851         int viaY = moveList[currentMove][6] - ONE;
11852         fromX = moveList[currentMove][0] - AAA;
11853         fromY = moveList[currentMove][1] - ONE;
11854
11855         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11856
11857         if(moveList[currentMove][4] == ';') { // multi-leg
11858             ChessSquare piece = boards[currentMove][viaY][viaX];
11859             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11860             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11861             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11862             boards[currentMove][viaY][viaX] = piece;
11863         } else
11864         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11865
11866         if (appData.highlightLastMove) {
11867             SetHighlights(fromX, fromY, toX, toY);
11868         }
11869     }
11870     DisplayMove(currentMove);
11871     SendMoveToProgram(currentMove++, &first);
11872     DisplayBothClocks();
11873     DrawPosition(FALSE, boards[currentMove]);
11874     // [HGM] PV info: always display, routine tests if empty
11875     DisplayComment(currentMove - 1, commentList[currentMove]);
11876     return TRUE;
11877 }
11878
11879
11880 int
11881 LoadGameOneMove (ChessMove readAhead)
11882 {
11883     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11884     char promoChar = NULLCHAR;
11885     ChessMove moveType;
11886     char move[MSG_SIZ];
11887     char *p, *q;
11888
11889     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11890         gameMode != AnalyzeMode && gameMode != Training) {
11891         gameFileFP = NULL;
11892         return FALSE;
11893     }
11894
11895     yyboardindex = forwardMostMove;
11896     if (readAhead != EndOfFile) {
11897       moveType = readAhead;
11898     } else {
11899       if (gameFileFP == NULL)
11900           return FALSE;
11901       moveType = (ChessMove) Myylex();
11902     }
11903
11904     done = FALSE;
11905     switch (moveType) {
11906       case Comment:
11907         if (appData.debugMode)
11908           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11909         p = yy_text;
11910
11911         /* append the comment but don't display it */
11912         AppendComment(currentMove, p, FALSE);
11913         return TRUE;
11914
11915       case WhiteCapturesEnPassant:
11916       case BlackCapturesEnPassant:
11917       case WhitePromotion:
11918       case BlackPromotion:
11919       case WhiteNonPromotion:
11920       case BlackNonPromotion:
11921       case NormalMove:
11922       case FirstLeg:
11923       case WhiteKingSideCastle:
11924       case WhiteQueenSideCastle:
11925       case BlackKingSideCastle:
11926       case BlackQueenSideCastle:
11927       case WhiteKingSideCastleWild:
11928       case WhiteQueenSideCastleWild:
11929       case BlackKingSideCastleWild:
11930       case BlackQueenSideCastleWild:
11931       /* PUSH Fabien */
11932       case WhiteHSideCastleFR:
11933       case WhiteASideCastleFR:
11934       case BlackHSideCastleFR:
11935       case BlackASideCastleFR:
11936       /* POP Fabien */
11937         if (appData.debugMode)
11938           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11939         fromX = currentMoveString[0] - AAA;
11940         fromY = currentMoveString[1] - ONE;
11941         toX = currentMoveString[2] - AAA;
11942         toY = currentMoveString[3] - ONE;
11943         promoChar = currentMoveString[4];
11944         if(promoChar == ';') promoChar = NULLCHAR;
11945         break;
11946
11947       case WhiteDrop:
11948       case BlackDrop:
11949         if (appData.debugMode)
11950           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11951         fromX = moveType == WhiteDrop ?
11952           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11953         (int) CharToPiece(ToLower(currentMoveString[0]));
11954         fromY = DROP_RANK;
11955         toX = currentMoveString[2] - AAA;
11956         toY = currentMoveString[3] - ONE;
11957         break;
11958
11959       case WhiteWins:
11960       case BlackWins:
11961       case GameIsDrawn:
11962       case GameUnfinished:
11963         if (appData.debugMode)
11964           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11965         p = strchr(yy_text, '{');
11966         if (p == NULL) p = strchr(yy_text, '(');
11967         if (p == NULL) {
11968             p = yy_text;
11969             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11970         } else {
11971             q = strchr(p, *p == '{' ? '}' : ')');
11972             if (q != NULL) *q = NULLCHAR;
11973             p++;
11974         }
11975         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11976         GameEnds(moveType, p, GE_FILE);
11977         done = TRUE;
11978         if (cmailMsgLoaded) {
11979             ClearHighlights();
11980             flipView = WhiteOnMove(currentMove);
11981             if (moveType == GameUnfinished) flipView = !flipView;
11982             if (appData.debugMode)
11983               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11984         }
11985         break;
11986
11987       case EndOfFile:
11988         if (appData.debugMode)
11989           fprintf(debugFP, "Parser hit end of file\n");
11990         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11991           case MT_NONE:
11992           case MT_CHECK:
11993             break;
11994           case MT_CHECKMATE:
11995           case MT_STAINMATE:
11996             if (WhiteOnMove(currentMove)) {
11997                 GameEnds(BlackWins, "Black mates", GE_FILE);
11998             } else {
11999                 GameEnds(WhiteWins, "White mates", GE_FILE);
12000             }
12001             break;
12002           case MT_STALEMATE:
12003             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12004             break;
12005         }
12006         done = TRUE;
12007         break;
12008
12009       case MoveNumberOne:
12010         if (lastLoadGameStart == GNUChessGame) {
12011             /* GNUChessGames have numbers, but they aren't move numbers */
12012             if (appData.debugMode)
12013               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12014                       yy_text, (int) moveType);
12015             return LoadGameOneMove(EndOfFile); /* tail recursion */
12016         }
12017         /* else fall thru */
12018
12019       case XBoardGame:
12020       case GNUChessGame:
12021       case PGNTag:
12022         /* Reached start of next game in file */
12023         if (appData.debugMode)
12024           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12025         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12026           case MT_NONE:
12027           case MT_CHECK:
12028             break;
12029           case MT_CHECKMATE:
12030           case MT_STAINMATE:
12031             if (WhiteOnMove(currentMove)) {
12032                 GameEnds(BlackWins, "Black mates", GE_FILE);
12033             } else {
12034                 GameEnds(WhiteWins, "White mates", GE_FILE);
12035             }
12036             break;
12037           case MT_STALEMATE:
12038             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12039             break;
12040         }
12041         done = TRUE;
12042         break;
12043
12044       case PositionDiagram:     /* should not happen; ignore */
12045       case ElapsedTime:         /* ignore */
12046       case NAG:                 /* ignore */
12047         if (appData.debugMode)
12048           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12049                   yy_text, (int) moveType);
12050         return LoadGameOneMove(EndOfFile); /* tail recursion */
12051
12052       case IllegalMove:
12053         if (appData.testLegality) {
12054             if (appData.debugMode)
12055               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12056             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12057                     (forwardMostMove / 2) + 1,
12058                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12059             DisplayError(move, 0);
12060             done = TRUE;
12061         } else {
12062             if (appData.debugMode)
12063               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12064                       yy_text, currentMoveString);
12065             fromX = currentMoveString[0] - AAA;
12066             fromY = currentMoveString[1] - ONE;
12067             toX = currentMoveString[2] - AAA;
12068             toY = currentMoveString[3] - ONE;
12069             promoChar = currentMoveString[4];
12070         }
12071         break;
12072
12073       case AmbiguousMove:
12074         if (appData.debugMode)
12075           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12076         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12077                 (forwardMostMove / 2) + 1,
12078                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12079         DisplayError(move, 0);
12080         done = TRUE;
12081         break;
12082
12083       default:
12084       case ImpossibleMove:
12085         if (appData.debugMode)
12086           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12087         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12088                 (forwardMostMove / 2) + 1,
12089                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12090         DisplayError(move, 0);
12091         done = TRUE;
12092         break;
12093     }
12094
12095     if (done) {
12096         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12097             DrawPosition(FALSE, boards[currentMove]);
12098             DisplayBothClocks();
12099             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12100               DisplayComment(currentMove - 1, commentList[currentMove]);
12101         }
12102         (void) StopLoadGameTimer();
12103         gameFileFP = NULL;
12104         cmailOldMove = forwardMostMove;
12105         return FALSE;
12106     } else {
12107         /* currentMoveString is set as a side-effect of yylex */
12108
12109         thinkOutput[0] = NULLCHAR;
12110         MakeMove(fromX, fromY, toX, toY, promoChar);
12111         killX = killY = -1; // [HGM] lion: used up
12112         currentMove = forwardMostMove;
12113         return TRUE;
12114     }
12115 }
12116
12117 /* Load the nth game from the given file */
12118 int
12119 LoadGameFromFile (char *filename, int n, char *title, int useList)
12120 {
12121     FILE *f;
12122     char buf[MSG_SIZ];
12123
12124     if (strcmp(filename, "-") == 0) {
12125         f = stdin;
12126         title = "stdin";
12127     } else {
12128         f = fopen(filename, "rb");
12129         if (f == NULL) {
12130           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12131             DisplayError(buf, errno);
12132             return FALSE;
12133         }
12134     }
12135     if (fseek(f, 0, 0) == -1) {
12136         /* f is not seekable; probably a pipe */
12137         useList = FALSE;
12138     }
12139     if (useList && n == 0) {
12140         int error = GameListBuild(f);
12141         if (error) {
12142             DisplayError(_("Cannot build game list"), error);
12143         } else if (!ListEmpty(&gameList) &&
12144                    ((ListGame *) gameList.tailPred)->number > 1) {
12145             GameListPopUp(f, title);
12146             return TRUE;
12147         }
12148         GameListDestroy();
12149         n = 1;
12150     }
12151     if (n == 0) n = 1;
12152     return LoadGame(f, n, title, FALSE);
12153 }
12154
12155
12156 void
12157 MakeRegisteredMove ()
12158 {
12159     int fromX, fromY, toX, toY;
12160     char promoChar;
12161     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12162         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12163           case CMAIL_MOVE:
12164           case CMAIL_DRAW:
12165             if (appData.debugMode)
12166               fprintf(debugFP, "Restoring %s for game %d\n",
12167                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12168
12169             thinkOutput[0] = NULLCHAR;
12170             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12171             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12172             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12173             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12174             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12175             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12176             MakeMove(fromX, fromY, toX, toY, promoChar);
12177             ShowMove(fromX, fromY, toX, toY);
12178
12179             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12180               case MT_NONE:
12181               case MT_CHECK:
12182                 break;
12183
12184               case MT_CHECKMATE:
12185               case MT_STAINMATE:
12186                 if (WhiteOnMove(currentMove)) {
12187                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12188                 } else {
12189                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12190                 }
12191                 break;
12192
12193               case MT_STALEMATE:
12194                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12195                 break;
12196             }
12197
12198             break;
12199
12200           case CMAIL_RESIGN:
12201             if (WhiteOnMove(currentMove)) {
12202                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12203             } else {
12204                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12205             }
12206             break;
12207
12208           case CMAIL_ACCEPT:
12209             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12210             break;
12211
12212           default:
12213             break;
12214         }
12215     }
12216
12217     return;
12218 }
12219
12220 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12221 int
12222 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12223 {
12224     int retVal;
12225
12226     if (gameNumber > nCmailGames) {
12227         DisplayError(_("No more games in this message"), 0);
12228         return FALSE;
12229     }
12230     if (f == lastLoadGameFP) {
12231         int offset = gameNumber - lastLoadGameNumber;
12232         if (offset == 0) {
12233             cmailMsg[0] = NULLCHAR;
12234             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12235                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12236                 nCmailMovesRegistered--;
12237             }
12238             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12239             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12240                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12241             }
12242         } else {
12243             if (! RegisterMove()) return FALSE;
12244         }
12245     }
12246
12247     retVal = LoadGame(f, gameNumber, title, useList);
12248
12249     /* Make move registered during previous look at this game, if any */
12250     MakeRegisteredMove();
12251
12252     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12253         commentList[currentMove]
12254           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12255         DisplayComment(currentMove - 1, commentList[currentMove]);
12256     }
12257
12258     return retVal;
12259 }
12260
12261 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12262 int
12263 ReloadGame (int offset)
12264 {
12265     int gameNumber = lastLoadGameNumber + offset;
12266     if (lastLoadGameFP == NULL) {
12267         DisplayError(_("No game has been loaded yet"), 0);
12268         return FALSE;
12269     }
12270     if (gameNumber <= 0) {
12271         DisplayError(_("Can't back up any further"), 0);
12272         return FALSE;
12273     }
12274     if (cmailMsgLoaded) {
12275         return CmailLoadGame(lastLoadGameFP, gameNumber,
12276                              lastLoadGameTitle, lastLoadGameUseList);
12277     } else {
12278         return LoadGame(lastLoadGameFP, gameNumber,
12279                         lastLoadGameTitle, lastLoadGameUseList);
12280     }
12281 }
12282
12283 int keys[EmptySquare+1];
12284
12285 int
12286 PositionMatches (Board b1, Board b2)
12287 {
12288     int r, f, sum=0;
12289     switch(appData.searchMode) {
12290         case 1: return CompareWithRights(b1, b2);
12291         case 2:
12292             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12293                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12294             }
12295             return TRUE;
12296         case 3:
12297             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12298               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12299                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12300             }
12301             return sum==0;
12302         case 4:
12303             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12304                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12305             }
12306             return sum==0;
12307     }
12308     return TRUE;
12309 }
12310
12311 #define Q_PROMO  4
12312 #define Q_EP     3
12313 #define Q_BCASTL 2
12314 #define Q_WCASTL 1
12315
12316 int pieceList[256], quickBoard[256];
12317 ChessSquare pieceType[256] = { EmptySquare };
12318 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12319 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12320 int soughtTotal, turn;
12321 Boolean epOK, flipSearch;
12322
12323 typedef struct {
12324     unsigned char piece, to;
12325 } Move;
12326
12327 #define DSIZE (250000)
12328
12329 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12330 Move *moveDatabase = initialSpace;
12331 unsigned int movePtr, dataSize = DSIZE;
12332
12333 int
12334 MakePieceList (Board board, int *counts)
12335 {
12336     int r, f, n=Q_PROMO, total=0;
12337     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12338     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12339         int sq = f + (r<<4);
12340         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12341             quickBoard[sq] = ++n;
12342             pieceList[n] = sq;
12343             pieceType[n] = board[r][f];
12344             counts[board[r][f]]++;
12345             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12346             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12347             total++;
12348         }
12349     }
12350     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12351     return total;
12352 }
12353
12354 void
12355 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12356 {
12357     int sq = fromX + (fromY<<4);
12358     int piece = quickBoard[sq], rook;
12359     quickBoard[sq] = 0;
12360     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12361     if(piece == pieceList[1] && fromY == toY) {
12362       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12363         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12364         moveDatabase[movePtr++].piece = Q_WCASTL;
12365         quickBoard[sq] = piece;
12366         piece = quickBoard[from]; quickBoard[from] = 0;
12367         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12368       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12369         quickBoard[sq] = 0; // remove Rook
12370         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12371         moveDatabase[movePtr++].piece = Q_WCASTL;
12372         quickBoard[sq] = pieceList[1]; // put King
12373         piece = rook;
12374         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12375       }
12376     } else
12377     if(piece == pieceList[2] && fromY == toY) {
12378       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12379         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12380         moveDatabase[movePtr++].piece = Q_BCASTL;
12381         quickBoard[sq] = piece;
12382         piece = quickBoard[from]; quickBoard[from] = 0;
12383         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12384       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12385         quickBoard[sq] = 0; // remove Rook
12386         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12387         moveDatabase[movePtr++].piece = Q_BCASTL;
12388         quickBoard[sq] = pieceList[2]; // put King
12389         piece = rook;
12390         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12391       }
12392     } else
12393     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12394         quickBoard[(fromY<<4)+toX] = 0;
12395         moveDatabase[movePtr].piece = Q_EP;
12396         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12397         moveDatabase[movePtr].to = sq;
12398     } else
12399     if(promoPiece != pieceType[piece]) {
12400         moveDatabase[movePtr++].piece = Q_PROMO;
12401         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12402     }
12403     moveDatabase[movePtr].piece = piece;
12404     quickBoard[sq] = piece;
12405     movePtr++;
12406 }
12407
12408 int
12409 PackGame (Board board)
12410 {
12411     Move *newSpace = NULL;
12412     moveDatabase[movePtr].piece = 0; // terminate previous game
12413     if(movePtr > dataSize) {
12414         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12415         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12416         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12417         if(newSpace) {
12418             int i;
12419             Move *p = moveDatabase, *q = newSpace;
12420             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12421             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12422             moveDatabase = newSpace;
12423         } else { // calloc failed, we must be out of memory. Too bad...
12424             dataSize = 0; // prevent calloc events for all subsequent games
12425             return 0;     // and signal this one isn't cached
12426         }
12427     }
12428     movePtr++;
12429     MakePieceList(board, counts);
12430     return movePtr;
12431 }
12432
12433 int
12434 QuickCompare (Board board, int *minCounts, int *maxCounts)
12435 {   // compare according to search mode
12436     int r, f;
12437     switch(appData.searchMode)
12438     {
12439       case 1: // exact position match
12440         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12441         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12442             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12443         }
12444         break;
12445       case 2: // can have extra material on empty squares
12446         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12447             if(board[r][f] == EmptySquare) continue;
12448             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12449         }
12450         break;
12451       case 3: // material with exact Pawn structure
12452         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12453             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12454             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12455         } // fall through to material comparison
12456       case 4: // exact material
12457         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12458         break;
12459       case 6: // material range with given imbalance
12460         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12461         // fall through to range comparison
12462       case 5: // material range
12463         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12464     }
12465     return TRUE;
12466 }
12467
12468 int
12469 QuickScan (Board board, Move *move)
12470 {   // reconstruct game,and compare all positions in it
12471     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12472     do {
12473         int piece = move->piece;
12474         int to = move->to, from = pieceList[piece];
12475         if(found < 0) { // if already found just scan to game end for final piece count
12476           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12477            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12478            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12479                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12480             ) {
12481             static int lastCounts[EmptySquare+1];
12482             int i;
12483             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12484             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12485           } else stretch = 0;
12486           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12487           if(found >= 0 && !appData.minPieces) return found;
12488         }
12489         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12490           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12491           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12492             piece = (++move)->piece;
12493             from = pieceList[piece];
12494             counts[pieceType[piece]]--;
12495             pieceType[piece] = (ChessSquare) move->to;
12496             counts[move->to]++;
12497           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12498             counts[pieceType[quickBoard[to]]]--;
12499             quickBoard[to] = 0; total--;
12500             move++;
12501             continue;
12502           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12503             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12504             from  = pieceList[piece]; // so this must be King
12505             quickBoard[from] = 0;
12506             pieceList[piece] = to;
12507             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12508             quickBoard[from] = 0; // rook
12509             quickBoard[to] = piece;
12510             to = move->to; piece = move->piece;
12511             goto aftercastle;
12512           }
12513         }
12514         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12515         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12516         quickBoard[from] = 0;
12517       aftercastle:
12518         quickBoard[to] = piece;
12519         pieceList[piece] = to;
12520         cnt++; turn ^= 3;
12521         move++;
12522     } while(1);
12523 }
12524
12525 void
12526 InitSearch ()
12527 {
12528     int r, f;
12529     flipSearch = FALSE;
12530     CopyBoard(soughtBoard, boards[currentMove]);
12531     soughtTotal = MakePieceList(soughtBoard, maxSought);
12532     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12533     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12534     CopyBoard(reverseBoard, boards[currentMove]);
12535     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12536         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12537         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12538         reverseBoard[r][f] = piece;
12539     }
12540     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12541     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12542     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12543                  || (boards[currentMove][CASTLING][2] == NoRights ||
12544                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12545                  && (boards[currentMove][CASTLING][5] == NoRights ||
12546                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12547       ) {
12548         flipSearch = TRUE;
12549         CopyBoard(flipBoard, soughtBoard);
12550         CopyBoard(rotateBoard, reverseBoard);
12551         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12552             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12553             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12554         }
12555     }
12556     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12557     if(appData.searchMode >= 5) {
12558         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12559         MakePieceList(soughtBoard, minSought);
12560         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12561     }
12562     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12563         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12564 }
12565
12566 GameInfo dummyInfo;
12567 static int creatingBook;
12568
12569 int
12570 GameContainsPosition (FILE *f, ListGame *lg)
12571 {
12572     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12573     int fromX, fromY, toX, toY;
12574     char promoChar;
12575     static int initDone=FALSE;
12576
12577     // weed out games based on numerical tag comparison
12578     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12579     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12580     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12581     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12582     if(!initDone) {
12583         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12584         initDone = TRUE;
12585     }
12586     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12587     else CopyBoard(boards[scratch], initialPosition); // default start position
12588     if(lg->moves) {
12589         turn = btm + 1;
12590         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12591         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12592     }
12593     if(btm) plyNr++;
12594     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12595     fseek(f, lg->offset, 0);
12596     yynewfile(f);
12597     while(1) {
12598         yyboardindex = scratch;
12599         quickFlag = plyNr+1;
12600         next = Myylex();
12601         quickFlag = 0;
12602         switch(next) {
12603             case PGNTag:
12604                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12605             default:
12606                 continue;
12607
12608             case XBoardGame:
12609             case GNUChessGame:
12610                 if(plyNr) return -1; // after we have seen moves, this is for new game
12611               continue;
12612
12613             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12614             case ImpossibleMove:
12615             case WhiteWins: // game ends here with these four
12616             case BlackWins:
12617             case GameIsDrawn:
12618             case GameUnfinished:
12619                 return -1;
12620
12621             case IllegalMove:
12622                 if(appData.testLegality) return -1;
12623             case WhiteCapturesEnPassant:
12624             case BlackCapturesEnPassant:
12625             case WhitePromotion:
12626             case BlackPromotion:
12627             case WhiteNonPromotion:
12628             case BlackNonPromotion:
12629             case NormalMove:
12630             case FirstLeg:
12631             case WhiteKingSideCastle:
12632             case WhiteQueenSideCastle:
12633             case BlackKingSideCastle:
12634             case BlackQueenSideCastle:
12635             case WhiteKingSideCastleWild:
12636             case WhiteQueenSideCastleWild:
12637             case BlackKingSideCastleWild:
12638             case BlackQueenSideCastleWild:
12639             case WhiteHSideCastleFR:
12640             case WhiteASideCastleFR:
12641             case BlackHSideCastleFR:
12642             case BlackASideCastleFR:
12643                 fromX = currentMoveString[0] - AAA;
12644                 fromY = currentMoveString[1] - ONE;
12645                 toX = currentMoveString[2] - AAA;
12646                 toY = currentMoveString[3] - ONE;
12647                 promoChar = currentMoveString[4];
12648                 break;
12649             case WhiteDrop:
12650             case BlackDrop:
12651                 fromX = next == WhiteDrop ?
12652                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12653                   (int) CharToPiece(ToLower(currentMoveString[0]));
12654                 fromY = DROP_RANK;
12655                 toX = currentMoveString[2] - AAA;
12656                 toY = currentMoveString[3] - ONE;
12657                 promoChar = 0;
12658                 break;
12659         }
12660         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12661         plyNr++;
12662         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12663         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12664         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12665         if(appData.findMirror) {
12666             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12667             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12668         }
12669     }
12670 }
12671
12672 /* Load the nth game from open file f */
12673 int
12674 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12675 {
12676     ChessMove cm;
12677     char buf[MSG_SIZ];
12678     int gn = gameNumber;
12679     ListGame *lg = NULL;
12680     int numPGNTags = 0;
12681     int err, pos = -1;
12682     GameMode oldGameMode;
12683     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12684
12685     if (appData.debugMode)
12686         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12687
12688     if (gameMode == Training )
12689         SetTrainingModeOff();
12690
12691     oldGameMode = gameMode;
12692     if (gameMode != BeginningOfGame) {
12693       Reset(FALSE, TRUE);
12694     }
12695     killX = killY = -1; // [HGM] lion: in case we did not Reset
12696
12697     gameFileFP = f;
12698     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12699         fclose(lastLoadGameFP);
12700     }
12701
12702     if (useList) {
12703         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12704
12705         if (lg) {
12706             fseek(f, lg->offset, 0);
12707             GameListHighlight(gameNumber);
12708             pos = lg->position;
12709             gn = 1;
12710         }
12711         else {
12712             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12713               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12714             else
12715             DisplayError(_("Game number out of range"), 0);
12716             return FALSE;
12717         }
12718     } else {
12719         GameListDestroy();
12720         if (fseek(f, 0, 0) == -1) {
12721             if (f == lastLoadGameFP ?
12722                 gameNumber == lastLoadGameNumber + 1 :
12723                 gameNumber == 1) {
12724                 gn = 1;
12725             } else {
12726                 DisplayError(_("Can't seek on game file"), 0);
12727                 return FALSE;
12728             }
12729         }
12730     }
12731     lastLoadGameFP = f;
12732     lastLoadGameNumber = gameNumber;
12733     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12734     lastLoadGameUseList = useList;
12735
12736     yynewfile(f);
12737
12738     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12739       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12740                 lg->gameInfo.black);
12741             DisplayTitle(buf);
12742     } else if (*title != NULLCHAR) {
12743         if (gameNumber > 1) {
12744           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12745             DisplayTitle(buf);
12746         } else {
12747             DisplayTitle(title);
12748         }
12749     }
12750
12751     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12752         gameMode = PlayFromGameFile;
12753         ModeHighlight();
12754     }
12755
12756     currentMove = forwardMostMove = backwardMostMove = 0;
12757     CopyBoard(boards[0], initialPosition);
12758     StopClocks();
12759
12760     /*
12761      * Skip the first gn-1 games in the file.
12762      * Also skip over anything that precedes an identifiable
12763      * start of game marker, to avoid being confused by
12764      * garbage at the start of the file.  Currently
12765      * recognized start of game markers are the move number "1",
12766      * the pattern "gnuchess .* game", the pattern
12767      * "^[#;%] [^ ]* game file", and a PGN tag block.
12768      * A game that starts with one of the latter two patterns
12769      * will also have a move number 1, possibly
12770      * following a position diagram.
12771      * 5-4-02: Let's try being more lenient and allowing a game to
12772      * start with an unnumbered move.  Does that break anything?
12773      */
12774     cm = lastLoadGameStart = EndOfFile;
12775     while (gn > 0) {
12776         yyboardindex = forwardMostMove;
12777         cm = (ChessMove) Myylex();
12778         switch (cm) {
12779           case EndOfFile:
12780             if (cmailMsgLoaded) {
12781                 nCmailGames = CMAIL_MAX_GAMES - gn;
12782             } else {
12783                 Reset(TRUE, TRUE);
12784                 DisplayError(_("Game not found in file"), 0);
12785             }
12786             return FALSE;
12787
12788           case GNUChessGame:
12789           case XBoardGame:
12790             gn--;
12791             lastLoadGameStart = cm;
12792             break;
12793
12794           case MoveNumberOne:
12795             switch (lastLoadGameStart) {
12796               case GNUChessGame:
12797               case XBoardGame:
12798               case PGNTag:
12799                 break;
12800               case MoveNumberOne:
12801               case EndOfFile:
12802                 gn--;           /* count this game */
12803                 lastLoadGameStart = cm;
12804                 break;
12805               default:
12806                 /* impossible */
12807                 break;
12808             }
12809             break;
12810
12811           case PGNTag:
12812             switch (lastLoadGameStart) {
12813               case GNUChessGame:
12814               case PGNTag:
12815               case MoveNumberOne:
12816               case EndOfFile:
12817                 gn--;           /* count this game */
12818                 lastLoadGameStart = cm;
12819                 break;
12820               case XBoardGame:
12821                 lastLoadGameStart = cm; /* game counted already */
12822                 break;
12823               default:
12824                 /* impossible */
12825                 break;
12826             }
12827             if (gn > 0) {
12828                 do {
12829                     yyboardindex = forwardMostMove;
12830                     cm = (ChessMove) Myylex();
12831                 } while (cm == PGNTag || cm == Comment);
12832             }
12833             break;
12834
12835           case WhiteWins:
12836           case BlackWins:
12837           case GameIsDrawn:
12838             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12839                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12840                     != CMAIL_OLD_RESULT) {
12841                     nCmailResults ++ ;
12842                     cmailResult[  CMAIL_MAX_GAMES
12843                                 - gn - 1] = CMAIL_OLD_RESULT;
12844                 }
12845             }
12846             break;
12847
12848           case NormalMove:
12849           case FirstLeg:
12850             /* Only a NormalMove can be at the start of a game
12851              * without a position diagram. */
12852             if (lastLoadGameStart == EndOfFile ) {
12853               gn--;
12854               lastLoadGameStart = MoveNumberOne;
12855             }
12856             break;
12857
12858           default:
12859             break;
12860         }
12861     }
12862
12863     if (appData.debugMode)
12864       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12865
12866     if (cm == XBoardGame) {
12867         /* Skip any header junk before position diagram and/or move 1 */
12868         for (;;) {
12869             yyboardindex = forwardMostMove;
12870             cm = (ChessMove) Myylex();
12871
12872             if (cm == EndOfFile ||
12873                 cm == GNUChessGame || cm == XBoardGame) {
12874                 /* Empty game; pretend end-of-file and handle later */
12875                 cm = EndOfFile;
12876                 break;
12877             }
12878
12879             if (cm == MoveNumberOne || cm == PositionDiagram ||
12880                 cm == PGNTag || cm == Comment)
12881               break;
12882         }
12883     } else if (cm == GNUChessGame) {
12884         if (gameInfo.event != NULL) {
12885             free(gameInfo.event);
12886         }
12887         gameInfo.event = StrSave(yy_text);
12888     }
12889
12890     startedFromSetupPosition = FALSE;
12891     while (cm == PGNTag) {
12892         if (appData.debugMode)
12893           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12894         err = ParsePGNTag(yy_text, &gameInfo);
12895         if (!err) numPGNTags++;
12896
12897         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12898         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12899             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12900             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12901             InitPosition(TRUE);
12902             oldVariant = gameInfo.variant;
12903             if (appData.debugMode)
12904               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12905         }
12906
12907
12908         if (gameInfo.fen != NULL) {
12909           Board initial_position;
12910           startedFromSetupPosition = TRUE;
12911           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12912             Reset(TRUE, TRUE);
12913             DisplayError(_("Bad FEN position in file"), 0);
12914             return FALSE;
12915           }
12916           CopyBoard(boards[0], initial_position);
12917           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12918             CopyBoard(initialPosition, initial_position);
12919           if (blackPlaysFirst) {
12920             currentMove = forwardMostMove = backwardMostMove = 1;
12921             CopyBoard(boards[1], initial_position);
12922             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12923             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12924             timeRemaining[0][1] = whiteTimeRemaining;
12925             timeRemaining[1][1] = blackTimeRemaining;
12926             if (commentList[0] != NULL) {
12927               commentList[1] = commentList[0];
12928               commentList[0] = NULL;
12929             }
12930           } else {
12931             currentMove = forwardMostMove = backwardMostMove = 0;
12932           }
12933           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12934           {   int i;
12935               initialRulePlies = FENrulePlies;
12936               for( i=0; i< nrCastlingRights; i++ )
12937                   initialRights[i] = initial_position[CASTLING][i];
12938           }
12939           yyboardindex = forwardMostMove;
12940           free(gameInfo.fen);
12941           gameInfo.fen = NULL;
12942         }
12943
12944         yyboardindex = forwardMostMove;
12945         cm = (ChessMove) Myylex();
12946
12947         /* Handle comments interspersed among the tags */
12948         while (cm == Comment) {
12949             char *p;
12950             if (appData.debugMode)
12951               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12952             p = yy_text;
12953             AppendComment(currentMove, p, FALSE);
12954             yyboardindex = forwardMostMove;
12955             cm = (ChessMove) Myylex();
12956         }
12957     }
12958
12959     /* don't rely on existence of Event tag since if game was
12960      * pasted from clipboard the Event tag may not exist
12961      */
12962     if (numPGNTags > 0){
12963         char *tags;
12964         if (gameInfo.variant == VariantNormal) {
12965           VariantClass v = StringToVariant(gameInfo.event);
12966           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12967           if(v < VariantShogi) gameInfo.variant = v;
12968         }
12969         if (!matchMode) {
12970           if( appData.autoDisplayTags ) {
12971             tags = PGNTags(&gameInfo);
12972             TagsPopUp(tags, CmailMsg());
12973             free(tags);
12974           }
12975         }
12976     } else {
12977         /* Make something up, but don't display it now */
12978         SetGameInfo();
12979         TagsPopDown();
12980     }
12981
12982     if (cm == PositionDiagram) {
12983         int i, j;
12984         char *p;
12985         Board initial_position;
12986
12987         if (appData.debugMode)
12988           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12989
12990         if (!startedFromSetupPosition) {
12991             p = yy_text;
12992             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12993               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12994                 switch (*p) {
12995                   case '{':
12996                   case '[':
12997                   case '-':
12998                   case ' ':
12999                   case '\t':
13000                   case '\n':
13001                   case '\r':
13002                     break;
13003                   default:
13004                     initial_position[i][j++] = CharToPiece(*p);
13005                     break;
13006                 }
13007             while (*p == ' ' || *p == '\t' ||
13008                    *p == '\n' || *p == '\r') p++;
13009
13010             if (strncmp(p, "black", strlen("black"))==0)
13011               blackPlaysFirst = TRUE;
13012             else
13013               blackPlaysFirst = FALSE;
13014             startedFromSetupPosition = TRUE;
13015
13016             CopyBoard(boards[0], initial_position);
13017             if (blackPlaysFirst) {
13018                 currentMove = forwardMostMove = backwardMostMove = 1;
13019                 CopyBoard(boards[1], initial_position);
13020                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13021                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13022                 timeRemaining[0][1] = whiteTimeRemaining;
13023                 timeRemaining[1][1] = blackTimeRemaining;
13024                 if (commentList[0] != NULL) {
13025                     commentList[1] = commentList[0];
13026                     commentList[0] = NULL;
13027                 }
13028             } else {
13029                 currentMove = forwardMostMove = backwardMostMove = 0;
13030             }
13031         }
13032         yyboardindex = forwardMostMove;
13033         cm = (ChessMove) Myylex();
13034     }
13035
13036   if(!creatingBook) {
13037     if (first.pr == NoProc) {
13038         StartChessProgram(&first);
13039     }
13040     InitChessProgram(&first, FALSE);
13041     SendToProgram("force\n", &first);
13042     if (startedFromSetupPosition) {
13043         SendBoard(&first, forwardMostMove);
13044     if (appData.debugMode) {
13045         fprintf(debugFP, "Load Game\n");
13046     }
13047         DisplayBothClocks();
13048     }
13049   }
13050
13051     /* [HGM] server: flag to write setup moves in broadcast file as one */
13052     loadFlag = appData.suppressLoadMoves;
13053
13054     while (cm == Comment) {
13055         char *p;
13056         if (appData.debugMode)
13057           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13058         p = yy_text;
13059         AppendComment(currentMove, p, FALSE);
13060         yyboardindex = forwardMostMove;
13061         cm = (ChessMove) Myylex();
13062     }
13063
13064     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13065         cm == WhiteWins || cm == BlackWins ||
13066         cm == GameIsDrawn || cm == GameUnfinished) {
13067         DisplayMessage("", _("No moves in game"));
13068         if (cmailMsgLoaded) {
13069             if (appData.debugMode)
13070               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13071             ClearHighlights();
13072             flipView = FALSE;
13073         }
13074         DrawPosition(FALSE, boards[currentMove]);
13075         DisplayBothClocks();
13076         gameMode = EditGame;
13077         ModeHighlight();
13078         gameFileFP = NULL;
13079         cmailOldMove = 0;
13080         return TRUE;
13081     }
13082
13083     // [HGM] PV info: routine tests if comment empty
13084     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13085         DisplayComment(currentMove - 1, commentList[currentMove]);
13086     }
13087     if (!matchMode && appData.timeDelay != 0)
13088       DrawPosition(FALSE, boards[currentMove]);
13089
13090     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13091       programStats.ok_to_send = 1;
13092     }
13093
13094     /* if the first token after the PGN tags is a move
13095      * and not move number 1, retrieve it from the parser
13096      */
13097     if (cm != MoveNumberOne)
13098         LoadGameOneMove(cm);
13099
13100     /* load the remaining moves from the file */
13101     while (LoadGameOneMove(EndOfFile)) {
13102       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13103       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13104     }
13105
13106     /* rewind to the start of the game */
13107     currentMove = backwardMostMove;
13108
13109     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13110
13111     if (oldGameMode == AnalyzeFile) {
13112       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13113       AnalyzeFileEvent();
13114     } else
13115     if (oldGameMode == AnalyzeMode) {
13116       AnalyzeFileEvent();
13117     }
13118
13119     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13120         long int w, b; // [HGM] adjourn: restore saved clock times
13121         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13122         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13123             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13124             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13125         }
13126     }
13127
13128     if(creatingBook) return TRUE;
13129     if (!matchMode && pos > 0) {
13130         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13131     } else
13132     if (matchMode || appData.timeDelay == 0) {
13133       ToEndEvent();
13134     } else if (appData.timeDelay > 0) {
13135       AutoPlayGameLoop();
13136     }
13137
13138     if (appData.debugMode)
13139         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13140
13141     loadFlag = 0; /* [HGM] true game starts */
13142     return TRUE;
13143 }
13144
13145 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13146 int
13147 ReloadPosition (int offset)
13148 {
13149     int positionNumber = lastLoadPositionNumber + offset;
13150     if (lastLoadPositionFP == NULL) {
13151         DisplayError(_("No position has been loaded yet"), 0);
13152         return FALSE;
13153     }
13154     if (positionNumber <= 0) {
13155         DisplayError(_("Can't back up any further"), 0);
13156         return FALSE;
13157     }
13158     return LoadPosition(lastLoadPositionFP, positionNumber,
13159                         lastLoadPositionTitle);
13160 }
13161
13162 /* Load the nth position from the given file */
13163 int
13164 LoadPositionFromFile (char *filename, int n, char *title)
13165 {
13166     FILE *f;
13167     char buf[MSG_SIZ];
13168
13169     if (strcmp(filename, "-") == 0) {
13170         return LoadPosition(stdin, n, "stdin");
13171     } else {
13172         f = fopen(filename, "rb");
13173         if (f == NULL) {
13174             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13175             DisplayError(buf, errno);
13176             return FALSE;
13177         } else {
13178             return LoadPosition(f, n, title);
13179         }
13180     }
13181 }
13182
13183 /* Load the nth position from the given open file, and close it */
13184 int
13185 LoadPosition (FILE *f, int positionNumber, char *title)
13186 {
13187     char *p, line[MSG_SIZ];
13188     Board initial_position;
13189     int i, j, fenMode, pn;
13190
13191     if (gameMode == Training )
13192         SetTrainingModeOff();
13193
13194     if (gameMode != BeginningOfGame) {
13195         Reset(FALSE, TRUE);
13196     }
13197     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13198         fclose(lastLoadPositionFP);
13199     }
13200     if (positionNumber == 0) positionNumber = 1;
13201     lastLoadPositionFP = f;
13202     lastLoadPositionNumber = positionNumber;
13203     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13204     if (first.pr == NoProc && !appData.noChessProgram) {
13205       StartChessProgram(&first);
13206       InitChessProgram(&first, FALSE);
13207     }
13208     pn = positionNumber;
13209     if (positionNumber < 0) {
13210         /* Negative position number means to seek to that byte offset */
13211         if (fseek(f, -positionNumber, 0) == -1) {
13212             DisplayError(_("Can't seek on position file"), 0);
13213             return FALSE;
13214         };
13215         pn = 1;
13216     } else {
13217         if (fseek(f, 0, 0) == -1) {
13218             if (f == lastLoadPositionFP ?
13219                 positionNumber == lastLoadPositionNumber + 1 :
13220                 positionNumber == 1) {
13221                 pn = 1;
13222             } else {
13223                 DisplayError(_("Can't seek on position file"), 0);
13224                 return FALSE;
13225             }
13226         }
13227     }
13228     /* See if this file is FEN or old-style xboard */
13229     if (fgets(line, MSG_SIZ, f) == NULL) {
13230         DisplayError(_("Position not found in file"), 0);
13231         return FALSE;
13232     }
13233     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13234     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13235
13236     if (pn >= 2) {
13237         if (fenMode || line[0] == '#') pn--;
13238         while (pn > 0) {
13239             /* skip positions before number pn */
13240             if (fgets(line, MSG_SIZ, f) == NULL) {
13241                 Reset(TRUE, TRUE);
13242                 DisplayError(_("Position not found in file"), 0);
13243                 return FALSE;
13244             }
13245             if (fenMode || line[0] == '#') pn--;
13246         }
13247     }
13248
13249     if (fenMode) {
13250         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13251             DisplayError(_("Bad FEN position in file"), 0);
13252             return FALSE;
13253         }
13254     } else {
13255         (void) fgets(line, MSG_SIZ, f);
13256         (void) fgets(line, MSG_SIZ, f);
13257
13258         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13259             (void) fgets(line, MSG_SIZ, f);
13260             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13261                 if (*p == ' ')
13262                   continue;
13263                 initial_position[i][j++] = CharToPiece(*p);
13264             }
13265         }
13266
13267         blackPlaysFirst = FALSE;
13268         if (!feof(f)) {
13269             (void) fgets(line, MSG_SIZ, f);
13270             if (strncmp(line, "black", strlen("black"))==0)
13271               blackPlaysFirst = TRUE;
13272         }
13273     }
13274     startedFromSetupPosition = TRUE;
13275
13276     CopyBoard(boards[0], initial_position);
13277     if (blackPlaysFirst) {
13278         currentMove = forwardMostMove = backwardMostMove = 1;
13279         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13280         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13281         CopyBoard(boards[1], initial_position);
13282         DisplayMessage("", _("Black to play"));
13283     } else {
13284         currentMove = forwardMostMove = backwardMostMove = 0;
13285         DisplayMessage("", _("White to play"));
13286     }
13287     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13288     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13289         SendToProgram("force\n", &first);
13290         SendBoard(&first, forwardMostMove);
13291     }
13292     if (appData.debugMode) {
13293 int i, j;
13294   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13295   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13296         fprintf(debugFP, "Load Position\n");
13297     }
13298
13299     if (positionNumber > 1) {
13300       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13301         DisplayTitle(line);
13302     } else {
13303         DisplayTitle(title);
13304     }
13305     gameMode = EditGame;
13306     ModeHighlight();
13307     ResetClocks();
13308     timeRemaining[0][1] = whiteTimeRemaining;
13309     timeRemaining[1][1] = blackTimeRemaining;
13310     DrawPosition(FALSE, boards[currentMove]);
13311
13312     return TRUE;
13313 }
13314
13315
13316 void
13317 CopyPlayerNameIntoFileName (char **dest, char *src)
13318 {
13319     while (*src != NULLCHAR && *src != ',') {
13320         if (*src == ' ') {
13321             *(*dest)++ = '_';
13322             src++;
13323         } else {
13324             *(*dest)++ = *src++;
13325         }
13326     }
13327 }
13328
13329 char *
13330 DefaultFileName (char *ext)
13331 {
13332     static char def[MSG_SIZ];
13333     char *p;
13334
13335     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13336         p = def;
13337         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13338         *p++ = '-';
13339         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13340         *p++ = '.';
13341         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13342     } else {
13343         def[0] = NULLCHAR;
13344     }
13345     return def;
13346 }
13347
13348 /* Save the current game to the given file */
13349 int
13350 SaveGameToFile (char *filename, int append)
13351 {
13352     FILE *f;
13353     char buf[MSG_SIZ];
13354     int result, i, t,tot=0;
13355
13356     if (strcmp(filename, "-") == 0) {
13357         return SaveGame(stdout, 0, NULL);
13358     } else {
13359         for(i=0; i<10; i++) { // upto 10 tries
13360              f = fopen(filename, append ? "a" : "w");
13361              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13362              if(f || errno != 13) break;
13363              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13364              tot += t;
13365         }
13366         if (f == NULL) {
13367             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13368             DisplayError(buf, errno);
13369             return FALSE;
13370         } else {
13371             safeStrCpy(buf, lastMsg, MSG_SIZ);
13372             DisplayMessage(_("Waiting for access to save file"), "");
13373             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13374             DisplayMessage(_("Saving game"), "");
13375             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13376             result = SaveGame(f, 0, NULL);
13377             DisplayMessage(buf, "");
13378             return result;
13379         }
13380     }
13381 }
13382
13383 char *
13384 SavePart (char *str)
13385 {
13386     static char buf[MSG_SIZ];
13387     char *p;
13388
13389     p = strchr(str, ' ');
13390     if (p == NULL) return str;
13391     strncpy(buf, str, p - str);
13392     buf[p - str] = NULLCHAR;
13393     return buf;
13394 }
13395
13396 #define PGN_MAX_LINE 75
13397
13398 #define PGN_SIDE_WHITE  0
13399 #define PGN_SIDE_BLACK  1
13400
13401 static int
13402 FindFirstMoveOutOfBook (int side)
13403 {
13404     int result = -1;
13405
13406     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13407         int index = backwardMostMove;
13408         int has_book_hit = 0;
13409
13410         if( (index % 2) != side ) {
13411             index++;
13412         }
13413
13414         while( index < forwardMostMove ) {
13415             /* Check to see if engine is in book */
13416             int depth = pvInfoList[index].depth;
13417             int score = pvInfoList[index].score;
13418             int in_book = 0;
13419
13420             if( depth <= 2 ) {
13421                 in_book = 1;
13422             }
13423             else if( score == 0 && depth == 63 ) {
13424                 in_book = 1; /* Zappa */
13425             }
13426             else if( score == 2 && depth == 99 ) {
13427                 in_book = 1; /* Abrok */
13428             }
13429
13430             has_book_hit += in_book;
13431
13432             if( ! in_book ) {
13433                 result = index;
13434
13435                 break;
13436             }
13437
13438             index += 2;
13439         }
13440     }
13441
13442     return result;
13443 }
13444
13445 void
13446 GetOutOfBookInfo (char * buf)
13447 {
13448     int oob[2];
13449     int i;
13450     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13451
13452     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13453     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13454
13455     *buf = '\0';
13456
13457     if( oob[0] >= 0 || oob[1] >= 0 ) {
13458         for( i=0; i<2; i++ ) {
13459             int idx = oob[i];
13460
13461             if( idx >= 0 ) {
13462                 if( i > 0 && oob[0] >= 0 ) {
13463                     strcat( buf, "   " );
13464                 }
13465
13466                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13467                 sprintf( buf+strlen(buf), "%s%.2f",
13468                     pvInfoList[idx].score >= 0 ? "+" : "",
13469                     pvInfoList[idx].score / 100.0 );
13470             }
13471         }
13472     }
13473 }
13474
13475 /* Save game in PGN style */
13476 static void
13477 SaveGamePGN2 (FILE *f)
13478 {
13479     int i, offset, linelen, newblock;
13480 //    char *movetext;
13481     char numtext[32];
13482     int movelen, numlen, blank;
13483     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13484
13485     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13486
13487     PrintPGNTags(f, &gameInfo);
13488
13489     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13490
13491     if (backwardMostMove > 0 || startedFromSetupPosition) {
13492         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13493         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13494         fprintf(f, "\n{--------------\n");
13495         PrintPosition(f, backwardMostMove);
13496         fprintf(f, "--------------}\n");
13497         free(fen);
13498     }
13499     else {
13500         /* [AS] Out of book annotation */
13501         if( appData.saveOutOfBookInfo ) {
13502             char buf[64];
13503
13504             GetOutOfBookInfo( buf );
13505
13506             if( buf[0] != '\0' ) {
13507                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13508             }
13509         }
13510
13511         fprintf(f, "\n");
13512     }
13513
13514     i = backwardMostMove;
13515     linelen = 0;
13516     newblock = TRUE;
13517
13518     while (i < forwardMostMove) {
13519         /* Print comments preceding this move */
13520         if (commentList[i] != NULL) {
13521             if (linelen > 0) fprintf(f, "\n");
13522             fprintf(f, "%s", commentList[i]);
13523             linelen = 0;
13524             newblock = TRUE;
13525         }
13526
13527         /* Format move number */
13528         if ((i % 2) == 0)
13529           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13530         else
13531           if (newblock)
13532             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13533           else
13534             numtext[0] = NULLCHAR;
13535
13536         numlen = strlen(numtext);
13537         newblock = FALSE;
13538
13539         /* Print move number */
13540         blank = linelen > 0 && numlen > 0;
13541         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13542             fprintf(f, "\n");
13543             linelen = 0;
13544             blank = 0;
13545         }
13546         if (blank) {
13547             fprintf(f, " ");
13548             linelen++;
13549         }
13550         fprintf(f, "%s", numtext);
13551         linelen += numlen;
13552
13553         /* Get move */
13554         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13555         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13556
13557         /* Print move */
13558         blank = linelen > 0 && movelen > 0;
13559         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13560             fprintf(f, "\n");
13561             linelen = 0;
13562             blank = 0;
13563         }
13564         if (blank) {
13565             fprintf(f, " ");
13566             linelen++;
13567         }
13568         fprintf(f, "%s", move_buffer);
13569         linelen += movelen;
13570
13571         /* [AS] Add PV info if present */
13572         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13573             /* [HGM] add time */
13574             char buf[MSG_SIZ]; int seconds;
13575
13576             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13577
13578             if( seconds <= 0)
13579               buf[0] = 0;
13580             else
13581               if( seconds < 30 )
13582                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13583               else
13584                 {
13585                   seconds = (seconds + 4)/10; // round to full seconds
13586                   if( seconds < 60 )
13587                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13588                   else
13589                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13590                 }
13591
13592             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13593                       pvInfoList[i].score >= 0 ? "+" : "",
13594                       pvInfoList[i].score / 100.0,
13595                       pvInfoList[i].depth,
13596                       buf );
13597
13598             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13599
13600             /* Print score/depth */
13601             blank = linelen > 0 && movelen > 0;
13602             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13603                 fprintf(f, "\n");
13604                 linelen = 0;
13605                 blank = 0;
13606             }
13607             if (blank) {
13608                 fprintf(f, " ");
13609                 linelen++;
13610             }
13611             fprintf(f, "%s", move_buffer);
13612             linelen += movelen;
13613         }
13614
13615         i++;
13616     }
13617
13618     /* Start a new line */
13619     if (linelen > 0) fprintf(f, "\n");
13620
13621     /* Print comments after last move */
13622     if (commentList[i] != NULL) {
13623         fprintf(f, "%s\n", commentList[i]);
13624     }
13625
13626     /* Print result */
13627     if (gameInfo.resultDetails != NULL &&
13628         gameInfo.resultDetails[0] != NULLCHAR) {
13629         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13630         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13631            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13632             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13633         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13634     } else {
13635         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13636     }
13637 }
13638
13639 /* Save game in PGN style and close the file */
13640 int
13641 SaveGamePGN (FILE *f)
13642 {
13643     SaveGamePGN2(f);
13644     fclose(f);
13645     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13646     return TRUE;
13647 }
13648
13649 /* Save game in old style and close the file */
13650 int
13651 SaveGameOldStyle (FILE *f)
13652 {
13653     int i, offset;
13654     time_t tm;
13655
13656     tm = time((time_t *) NULL);
13657
13658     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13659     PrintOpponents(f);
13660
13661     if (backwardMostMove > 0 || startedFromSetupPosition) {
13662         fprintf(f, "\n[--------------\n");
13663         PrintPosition(f, backwardMostMove);
13664         fprintf(f, "--------------]\n");
13665     } else {
13666         fprintf(f, "\n");
13667     }
13668
13669     i = backwardMostMove;
13670     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13671
13672     while (i < forwardMostMove) {
13673         if (commentList[i] != NULL) {
13674             fprintf(f, "[%s]\n", commentList[i]);
13675         }
13676
13677         if ((i % 2) == 1) {
13678             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13679             i++;
13680         } else {
13681             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13682             i++;
13683             if (commentList[i] != NULL) {
13684                 fprintf(f, "\n");
13685                 continue;
13686             }
13687             if (i >= forwardMostMove) {
13688                 fprintf(f, "\n");
13689                 break;
13690             }
13691             fprintf(f, "%s\n", parseList[i]);
13692             i++;
13693         }
13694     }
13695
13696     if (commentList[i] != NULL) {
13697         fprintf(f, "[%s]\n", commentList[i]);
13698     }
13699
13700     /* This isn't really the old style, but it's close enough */
13701     if (gameInfo.resultDetails != NULL &&
13702         gameInfo.resultDetails[0] != NULLCHAR) {
13703         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13704                 gameInfo.resultDetails);
13705     } else {
13706         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13707     }
13708
13709     fclose(f);
13710     return TRUE;
13711 }
13712
13713 /* Save the current game to open file f and close the file */
13714 int
13715 SaveGame (FILE *f, int dummy, char *dummy2)
13716 {
13717     if (gameMode == EditPosition) EditPositionDone(TRUE);
13718     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13719     if (appData.oldSaveStyle)
13720       return SaveGameOldStyle(f);
13721     else
13722       return SaveGamePGN(f);
13723 }
13724
13725 /* Save the current position to the given file */
13726 int
13727 SavePositionToFile (char *filename)
13728 {
13729     FILE *f;
13730     char buf[MSG_SIZ];
13731
13732     if (strcmp(filename, "-") == 0) {
13733         return SavePosition(stdout, 0, NULL);
13734     } else {
13735         f = fopen(filename, "a");
13736         if (f == NULL) {
13737             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13738             DisplayError(buf, errno);
13739             return FALSE;
13740         } else {
13741             safeStrCpy(buf, lastMsg, MSG_SIZ);
13742             DisplayMessage(_("Waiting for access to save file"), "");
13743             flock(fileno(f), LOCK_EX); // [HGM] lock
13744             DisplayMessage(_("Saving position"), "");
13745             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13746             SavePosition(f, 0, NULL);
13747             DisplayMessage(buf, "");
13748             return TRUE;
13749         }
13750     }
13751 }
13752
13753 /* Save the current position to the given open file and close the file */
13754 int
13755 SavePosition (FILE *f, int dummy, char *dummy2)
13756 {
13757     time_t tm;
13758     char *fen;
13759
13760     if (gameMode == EditPosition) EditPositionDone(TRUE);
13761     if (appData.oldSaveStyle) {
13762         tm = time((time_t *) NULL);
13763
13764         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13765         PrintOpponents(f);
13766         fprintf(f, "[--------------\n");
13767         PrintPosition(f, currentMove);
13768         fprintf(f, "--------------]\n");
13769     } else {
13770         fen = PositionToFEN(currentMove, NULL, 1);
13771         fprintf(f, "%s\n", fen);
13772         free(fen);
13773     }
13774     fclose(f);
13775     return TRUE;
13776 }
13777
13778 void
13779 ReloadCmailMsgEvent (int unregister)
13780 {
13781 #if !WIN32
13782     static char *inFilename = NULL;
13783     static char *outFilename;
13784     int i;
13785     struct stat inbuf, outbuf;
13786     int status;
13787
13788     /* Any registered moves are unregistered if unregister is set, */
13789     /* i.e. invoked by the signal handler */
13790     if (unregister) {
13791         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13792             cmailMoveRegistered[i] = FALSE;
13793             if (cmailCommentList[i] != NULL) {
13794                 free(cmailCommentList[i]);
13795                 cmailCommentList[i] = NULL;
13796             }
13797         }
13798         nCmailMovesRegistered = 0;
13799     }
13800
13801     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13802         cmailResult[i] = CMAIL_NOT_RESULT;
13803     }
13804     nCmailResults = 0;
13805
13806     if (inFilename == NULL) {
13807         /* Because the filenames are static they only get malloced once  */
13808         /* and they never get freed                                      */
13809         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13810         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13811
13812         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13813         sprintf(outFilename, "%s.out", appData.cmailGameName);
13814     }
13815
13816     status = stat(outFilename, &outbuf);
13817     if (status < 0) {
13818         cmailMailedMove = FALSE;
13819     } else {
13820         status = stat(inFilename, &inbuf);
13821         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13822     }
13823
13824     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13825        counts the games, notes how each one terminated, etc.
13826
13827        It would be nice to remove this kludge and instead gather all
13828        the information while building the game list.  (And to keep it
13829        in the game list nodes instead of having a bunch of fixed-size
13830        parallel arrays.)  Note this will require getting each game's
13831        termination from the PGN tags, as the game list builder does
13832        not process the game moves.  --mann
13833        */
13834     cmailMsgLoaded = TRUE;
13835     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13836
13837     /* Load first game in the file or popup game menu */
13838     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13839
13840 #endif /* !WIN32 */
13841     return;
13842 }
13843
13844 int
13845 RegisterMove ()
13846 {
13847     FILE *f;
13848     char string[MSG_SIZ];
13849
13850     if (   cmailMailedMove
13851         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13852         return TRUE;            /* Allow free viewing  */
13853     }
13854
13855     /* Unregister move to ensure that we don't leave RegisterMove        */
13856     /* with the move registered when the conditions for registering no   */
13857     /* longer hold                                                       */
13858     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13859         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13860         nCmailMovesRegistered --;
13861
13862         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13863           {
13864               free(cmailCommentList[lastLoadGameNumber - 1]);
13865               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13866           }
13867     }
13868
13869     if (cmailOldMove == -1) {
13870         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13871         return FALSE;
13872     }
13873
13874     if (currentMove > cmailOldMove + 1) {
13875         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13876         return FALSE;
13877     }
13878
13879     if (currentMove < cmailOldMove) {
13880         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13881         return FALSE;
13882     }
13883
13884     if (forwardMostMove > currentMove) {
13885         /* Silently truncate extra moves */
13886         TruncateGame();
13887     }
13888
13889     if (   (currentMove == cmailOldMove + 1)
13890         || (   (currentMove == cmailOldMove)
13891             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13892                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13893         if (gameInfo.result != GameUnfinished) {
13894             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13895         }
13896
13897         if (commentList[currentMove] != NULL) {
13898             cmailCommentList[lastLoadGameNumber - 1]
13899               = StrSave(commentList[currentMove]);
13900         }
13901         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13902
13903         if (appData.debugMode)
13904           fprintf(debugFP, "Saving %s for game %d\n",
13905                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13906
13907         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13908
13909         f = fopen(string, "w");
13910         if (appData.oldSaveStyle) {
13911             SaveGameOldStyle(f); /* also closes the file */
13912
13913             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13914             f = fopen(string, "w");
13915             SavePosition(f, 0, NULL); /* also closes the file */
13916         } else {
13917             fprintf(f, "{--------------\n");
13918             PrintPosition(f, currentMove);
13919             fprintf(f, "--------------}\n\n");
13920
13921             SaveGame(f, 0, NULL); /* also closes the file*/
13922         }
13923
13924         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13925         nCmailMovesRegistered ++;
13926     } else if (nCmailGames == 1) {
13927         DisplayError(_("You have not made a move yet"), 0);
13928         return FALSE;
13929     }
13930
13931     return TRUE;
13932 }
13933
13934 void
13935 MailMoveEvent ()
13936 {
13937 #if !WIN32
13938     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13939     FILE *commandOutput;
13940     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13941     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13942     int nBuffers;
13943     int i;
13944     int archived;
13945     char *arcDir;
13946
13947     if (! cmailMsgLoaded) {
13948         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13949         return;
13950     }
13951
13952     if (nCmailGames == nCmailResults) {
13953         DisplayError(_("No unfinished games"), 0);
13954         return;
13955     }
13956
13957 #if CMAIL_PROHIBIT_REMAIL
13958     if (cmailMailedMove) {
13959       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);
13960         DisplayError(msg, 0);
13961         return;
13962     }
13963 #endif
13964
13965     if (! (cmailMailedMove || RegisterMove())) return;
13966
13967     if (   cmailMailedMove
13968         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13969       snprintf(string, MSG_SIZ, partCommandString,
13970                appData.debugMode ? " -v" : "", appData.cmailGameName);
13971         commandOutput = popen(string, "r");
13972
13973         if (commandOutput == NULL) {
13974             DisplayError(_("Failed to invoke cmail"), 0);
13975         } else {
13976             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13977                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13978             }
13979             if (nBuffers > 1) {
13980                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13981                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13982                 nBytes = MSG_SIZ - 1;
13983             } else {
13984                 (void) memcpy(msg, buffer, nBytes);
13985             }
13986             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13987
13988             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13989                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13990
13991                 archived = TRUE;
13992                 for (i = 0; i < nCmailGames; i ++) {
13993                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13994                         archived = FALSE;
13995                     }
13996                 }
13997                 if (   archived
13998                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13999                         != NULL)) {
14000                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14001                            arcDir,
14002                            appData.cmailGameName,
14003                            gameInfo.date);
14004                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14005                     cmailMsgLoaded = FALSE;
14006                 }
14007             }
14008
14009             DisplayInformation(msg);
14010             pclose(commandOutput);
14011         }
14012     } else {
14013         if ((*cmailMsg) != '\0') {
14014             DisplayInformation(cmailMsg);
14015         }
14016     }
14017
14018     return;
14019 #endif /* !WIN32 */
14020 }
14021
14022 char *
14023 CmailMsg ()
14024 {
14025 #if WIN32
14026     return NULL;
14027 #else
14028     int  prependComma = 0;
14029     char number[5];
14030     char string[MSG_SIZ];       /* Space for game-list */
14031     int  i;
14032
14033     if (!cmailMsgLoaded) return "";
14034
14035     if (cmailMailedMove) {
14036       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14037     } else {
14038         /* Create a list of games left */
14039       snprintf(string, MSG_SIZ, "[");
14040         for (i = 0; i < nCmailGames; i ++) {
14041             if (! (   cmailMoveRegistered[i]
14042                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14043                 if (prependComma) {
14044                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14045                 } else {
14046                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14047                     prependComma = 1;
14048                 }
14049
14050                 strcat(string, number);
14051             }
14052         }
14053         strcat(string, "]");
14054
14055         if (nCmailMovesRegistered + nCmailResults == 0) {
14056             switch (nCmailGames) {
14057               case 1:
14058                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14059                 break;
14060
14061               case 2:
14062                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14063                 break;
14064
14065               default:
14066                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14067                          nCmailGames);
14068                 break;
14069             }
14070         } else {
14071             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14072               case 1:
14073                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14074                          string);
14075                 break;
14076
14077               case 0:
14078                 if (nCmailResults == nCmailGames) {
14079                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14080                 } else {
14081                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14082                 }
14083                 break;
14084
14085               default:
14086                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14087                          string);
14088             }
14089         }
14090     }
14091     return cmailMsg;
14092 #endif /* WIN32 */
14093 }
14094
14095 void
14096 ResetGameEvent ()
14097 {
14098     if (gameMode == Training)
14099       SetTrainingModeOff();
14100
14101     Reset(TRUE, TRUE);
14102     cmailMsgLoaded = FALSE;
14103     if (appData.icsActive) {
14104       SendToICS(ics_prefix);
14105       SendToICS("refresh\n");
14106     }
14107 }
14108
14109 void
14110 ExitEvent (int status)
14111 {
14112     exiting++;
14113     if (exiting > 2) {
14114       /* Give up on clean exit */
14115       exit(status);
14116     }
14117     if (exiting > 1) {
14118       /* Keep trying for clean exit */
14119       return;
14120     }
14121
14122     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14123     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14124
14125     if (telnetISR != NULL) {
14126       RemoveInputSource(telnetISR);
14127     }
14128     if (icsPR != NoProc) {
14129       DestroyChildProcess(icsPR, TRUE);
14130     }
14131
14132     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14133     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14134
14135     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14136     /* make sure this other one finishes before killing it!                  */
14137     if(endingGame) { int count = 0;
14138         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14139         while(endingGame && count++ < 10) DoSleep(1);
14140         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14141     }
14142
14143     /* Kill off chess programs */
14144     if (first.pr != NoProc) {
14145         ExitAnalyzeMode();
14146
14147         DoSleep( appData.delayBeforeQuit );
14148         SendToProgram("quit\n", &first);
14149         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14150     }
14151     if (second.pr != NoProc) {
14152         DoSleep( appData.delayBeforeQuit );
14153         SendToProgram("quit\n", &second);
14154         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14155     }
14156     if (first.isr != NULL) {
14157         RemoveInputSource(first.isr);
14158     }
14159     if (second.isr != NULL) {
14160         RemoveInputSource(second.isr);
14161     }
14162
14163     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14164     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14165
14166     ShutDownFrontEnd();
14167     exit(status);
14168 }
14169
14170 void
14171 PauseEngine (ChessProgramState *cps)
14172 {
14173     SendToProgram("pause\n", cps);
14174     cps->pause = 2;
14175 }
14176
14177 void
14178 UnPauseEngine (ChessProgramState *cps)
14179 {
14180     SendToProgram("resume\n", cps);
14181     cps->pause = 1;
14182 }
14183
14184 void
14185 PauseEvent ()
14186 {
14187     if (appData.debugMode)
14188         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14189     if (pausing) {
14190         pausing = FALSE;
14191         ModeHighlight();
14192         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14193             StartClocks();
14194             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14195                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14196                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14197             }
14198             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14199             HandleMachineMove(stashedInputMove, stalledEngine);
14200             stalledEngine = NULL;
14201             return;
14202         }
14203         if (gameMode == MachinePlaysWhite ||
14204             gameMode == TwoMachinesPlay   ||
14205             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14206             if(first.pause)  UnPauseEngine(&first);
14207             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14208             if(second.pause) UnPauseEngine(&second);
14209             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14210             StartClocks();
14211         } else {
14212             DisplayBothClocks();
14213         }
14214         if (gameMode == PlayFromGameFile) {
14215             if (appData.timeDelay >= 0)
14216                 AutoPlayGameLoop();
14217         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14218             Reset(FALSE, TRUE);
14219             SendToICS(ics_prefix);
14220             SendToICS("refresh\n");
14221         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14222             ForwardInner(forwardMostMove);
14223         }
14224         pauseExamInvalid = FALSE;
14225     } else {
14226         switch (gameMode) {
14227           default:
14228             return;
14229           case IcsExamining:
14230             pauseExamForwardMostMove = forwardMostMove;
14231             pauseExamInvalid = FALSE;
14232             /* fall through */
14233           case IcsObserving:
14234           case IcsPlayingWhite:
14235           case IcsPlayingBlack:
14236             pausing = TRUE;
14237             ModeHighlight();
14238             return;
14239           case PlayFromGameFile:
14240             (void) StopLoadGameTimer();
14241             pausing = TRUE;
14242             ModeHighlight();
14243             break;
14244           case BeginningOfGame:
14245             if (appData.icsActive) return;
14246             /* else fall through */
14247           case MachinePlaysWhite:
14248           case MachinePlaysBlack:
14249           case TwoMachinesPlay:
14250             if (forwardMostMove == 0)
14251               return;           /* don't pause if no one has moved */
14252             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14253                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14254                 if(onMove->pause) {           // thinking engine can be paused
14255                     PauseEngine(onMove);      // do it
14256                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14257                         PauseEngine(onMove->other);
14258                     else
14259                         SendToProgram("easy\n", onMove->other);
14260                     StopClocks();
14261                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14262             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14263                 if(first.pause) {
14264                     PauseEngine(&first);
14265                     StopClocks();
14266                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14267             } else { // human on move, pause pondering by either method
14268                 if(first.pause)
14269                     PauseEngine(&first);
14270                 else if(appData.ponderNextMove)
14271                     SendToProgram("easy\n", &first);
14272                 StopClocks();
14273             }
14274             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14275           case AnalyzeMode:
14276             pausing = TRUE;
14277             ModeHighlight();
14278             break;
14279         }
14280     }
14281 }
14282
14283 void
14284 EditCommentEvent ()
14285 {
14286     char title[MSG_SIZ];
14287
14288     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14289       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14290     } else {
14291       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14292                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14293                parseList[currentMove - 1]);
14294     }
14295
14296     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14297 }
14298
14299
14300 void
14301 EditTagsEvent ()
14302 {
14303     char *tags = PGNTags(&gameInfo);
14304     bookUp = FALSE;
14305     EditTagsPopUp(tags, NULL);
14306     free(tags);
14307 }
14308
14309 void
14310 ToggleSecond ()
14311 {
14312   if(second.analyzing) {
14313     SendToProgram("exit\n", &second);
14314     second.analyzing = FALSE;
14315   } else {
14316     if (second.pr == NoProc) StartChessProgram(&second);
14317     InitChessProgram(&second, FALSE);
14318     FeedMovesToProgram(&second, currentMove);
14319
14320     SendToProgram("analyze\n", &second);
14321     second.analyzing = TRUE;
14322   }
14323 }
14324
14325 /* Toggle ShowThinking */
14326 void
14327 ToggleShowThinking()
14328 {
14329   appData.showThinking = !appData.showThinking;
14330   ShowThinkingEvent();
14331 }
14332
14333 int
14334 AnalyzeModeEvent ()
14335 {
14336     char buf[MSG_SIZ];
14337
14338     if (!first.analysisSupport) {
14339       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14340       DisplayError(buf, 0);
14341       return 0;
14342     }
14343     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14344     if (appData.icsActive) {
14345         if (gameMode != IcsObserving) {
14346           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14347             DisplayError(buf, 0);
14348             /* secure check */
14349             if (appData.icsEngineAnalyze) {
14350                 if (appData.debugMode)
14351                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14352                 ExitAnalyzeMode();
14353                 ModeHighlight();
14354             }
14355             return 0;
14356         }
14357         /* if enable, user wants to disable icsEngineAnalyze */
14358         if (appData.icsEngineAnalyze) {
14359                 ExitAnalyzeMode();
14360                 ModeHighlight();
14361                 return 0;
14362         }
14363         appData.icsEngineAnalyze = TRUE;
14364         if (appData.debugMode)
14365             fprintf(debugFP, "ICS engine analyze starting... \n");
14366     }
14367
14368     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14369     if (appData.noChessProgram || gameMode == AnalyzeMode)
14370       return 0;
14371
14372     if (gameMode != AnalyzeFile) {
14373         if (!appData.icsEngineAnalyze) {
14374                EditGameEvent();
14375                if (gameMode != EditGame) return 0;
14376         }
14377         if (!appData.showThinking) ToggleShowThinking();
14378         ResurrectChessProgram();
14379         SendToProgram("analyze\n", &first);
14380         first.analyzing = TRUE;
14381         /*first.maybeThinking = TRUE;*/
14382         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14383         EngineOutputPopUp();
14384     }
14385     if (!appData.icsEngineAnalyze) {
14386         gameMode = AnalyzeMode;
14387         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14388     }
14389     pausing = FALSE;
14390     ModeHighlight();
14391     SetGameInfo();
14392
14393     StartAnalysisClock();
14394     GetTimeMark(&lastNodeCountTime);
14395     lastNodeCount = 0;
14396     return 1;
14397 }
14398
14399 void
14400 AnalyzeFileEvent ()
14401 {
14402     if (appData.noChessProgram || gameMode == AnalyzeFile)
14403       return;
14404
14405     if (!first.analysisSupport) {
14406       char buf[MSG_SIZ];
14407       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14408       DisplayError(buf, 0);
14409       return;
14410     }
14411
14412     if (gameMode != AnalyzeMode) {
14413         keepInfo = 1; // mere annotating should not alter PGN tags
14414         EditGameEvent();
14415         keepInfo = 0;
14416         if (gameMode != EditGame) return;
14417         if (!appData.showThinking) ToggleShowThinking();
14418         ResurrectChessProgram();
14419         SendToProgram("analyze\n", &first);
14420         first.analyzing = TRUE;
14421         /*first.maybeThinking = TRUE;*/
14422         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14423         EngineOutputPopUp();
14424     }
14425     gameMode = AnalyzeFile;
14426     pausing = FALSE;
14427     ModeHighlight();
14428
14429     StartAnalysisClock();
14430     GetTimeMark(&lastNodeCountTime);
14431     lastNodeCount = 0;
14432     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14433     AnalysisPeriodicEvent(1);
14434 }
14435
14436 void
14437 MachineWhiteEvent ()
14438 {
14439     char buf[MSG_SIZ];
14440     char *bookHit = NULL;
14441
14442     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14443       return;
14444
14445
14446     if (gameMode == PlayFromGameFile ||
14447         gameMode == TwoMachinesPlay  ||
14448         gameMode == Training         ||
14449         gameMode == AnalyzeMode      ||
14450         gameMode == EndOfGame)
14451         EditGameEvent();
14452
14453     if (gameMode == EditPosition)
14454         EditPositionDone(TRUE);
14455
14456     if (!WhiteOnMove(currentMove)) {
14457         DisplayError(_("It is not White's turn"), 0);
14458         return;
14459     }
14460
14461     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14462       ExitAnalyzeMode();
14463
14464     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14465         gameMode == AnalyzeFile)
14466         TruncateGame();
14467
14468     ResurrectChessProgram();    /* in case it isn't running */
14469     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14470         gameMode = MachinePlaysWhite;
14471         ResetClocks();
14472     } else
14473     gameMode = MachinePlaysWhite;
14474     pausing = FALSE;
14475     ModeHighlight();
14476     SetGameInfo();
14477     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14478     DisplayTitle(buf);
14479     if (first.sendName) {
14480       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14481       SendToProgram(buf, &first);
14482     }
14483     if (first.sendTime) {
14484       if (first.useColors) {
14485         SendToProgram("black\n", &first); /*gnu kludge*/
14486       }
14487       SendTimeRemaining(&first, TRUE);
14488     }
14489     if (first.useColors) {
14490       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14491     }
14492     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14493     SetMachineThinkingEnables();
14494     first.maybeThinking = TRUE;
14495     StartClocks();
14496     firstMove = FALSE;
14497
14498     if (appData.autoFlipView && !flipView) {
14499       flipView = !flipView;
14500       DrawPosition(FALSE, NULL);
14501       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14502     }
14503
14504     if(bookHit) { // [HGM] book: simulate book reply
14505         static char bookMove[MSG_SIZ]; // a bit generous?
14506
14507         programStats.nodes = programStats.depth = programStats.time =
14508         programStats.score = programStats.got_only_move = 0;
14509         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14510
14511         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14512         strcat(bookMove, bookHit);
14513         HandleMachineMove(bookMove, &first);
14514     }
14515 }
14516
14517 void
14518 MachineBlackEvent ()
14519 {
14520   char buf[MSG_SIZ];
14521   char *bookHit = NULL;
14522
14523     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14524         return;
14525
14526
14527     if (gameMode == PlayFromGameFile ||
14528         gameMode == TwoMachinesPlay  ||
14529         gameMode == Training         ||
14530         gameMode == AnalyzeMode      ||
14531         gameMode == EndOfGame)
14532         EditGameEvent();
14533
14534     if (gameMode == EditPosition)
14535         EditPositionDone(TRUE);
14536
14537     if (WhiteOnMove(currentMove)) {
14538         DisplayError(_("It is not Black's turn"), 0);
14539         return;
14540     }
14541
14542     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14543       ExitAnalyzeMode();
14544
14545     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14546         gameMode == AnalyzeFile)
14547         TruncateGame();
14548
14549     ResurrectChessProgram();    /* in case it isn't running */
14550     gameMode = MachinePlaysBlack;
14551     pausing = FALSE;
14552     ModeHighlight();
14553     SetGameInfo();
14554     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14555     DisplayTitle(buf);
14556     if (first.sendName) {
14557       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14558       SendToProgram(buf, &first);
14559     }
14560     if (first.sendTime) {
14561       if (first.useColors) {
14562         SendToProgram("white\n", &first); /*gnu kludge*/
14563       }
14564       SendTimeRemaining(&first, FALSE);
14565     }
14566     if (first.useColors) {
14567       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14568     }
14569     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14570     SetMachineThinkingEnables();
14571     first.maybeThinking = TRUE;
14572     StartClocks();
14573
14574     if (appData.autoFlipView && flipView) {
14575       flipView = !flipView;
14576       DrawPosition(FALSE, NULL);
14577       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14578     }
14579     if(bookHit) { // [HGM] book: simulate book reply
14580         static char bookMove[MSG_SIZ]; // a bit generous?
14581
14582         programStats.nodes = programStats.depth = programStats.time =
14583         programStats.score = programStats.got_only_move = 0;
14584         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14585
14586         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14587         strcat(bookMove, bookHit);
14588         HandleMachineMove(bookMove, &first);
14589     }
14590 }
14591
14592
14593 void
14594 DisplayTwoMachinesTitle ()
14595 {
14596     char buf[MSG_SIZ];
14597     if (appData.matchGames > 0) {
14598         if(appData.tourneyFile[0]) {
14599           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14600                    gameInfo.white, _("vs."), gameInfo.black,
14601                    nextGame+1, appData.matchGames+1,
14602                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14603         } else
14604         if (first.twoMachinesColor[0] == 'w') {
14605           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14606                    gameInfo.white, _("vs."),  gameInfo.black,
14607                    first.matchWins, second.matchWins,
14608                    matchGame - 1 - (first.matchWins + second.matchWins));
14609         } else {
14610           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14611                    gameInfo.white, _("vs."), gameInfo.black,
14612                    second.matchWins, first.matchWins,
14613                    matchGame - 1 - (first.matchWins + second.matchWins));
14614         }
14615     } else {
14616       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14617     }
14618     DisplayTitle(buf);
14619 }
14620
14621 void
14622 SettingsMenuIfReady ()
14623 {
14624   if (second.lastPing != second.lastPong) {
14625     DisplayMessage("", _("Waiting for second chess program"));
14626     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14627     return;
14628   }
14629   ThawUI();
14630   DisplayMessage("", "");
14631   SettingsPopUp(&second);
14632 }
14633
14634 int
14635 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14636 {
14637     char buf[MSG_SIZ];
14638     if (cps->pr == NoProc) {
14639         StartChessProgram(cps);
14640         if (cps->protocolVersion == 1) {
14641           retry();
14642           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14643         } else {
14644           /* kludge: allow timeout for initial "feature" command */
14645           if(retry != TwoMachinesEventIfReady) FreezeUI();
14646           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14647           DisplayMessage("", buf);
14648           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14649         }
14650         return 1;
14651     }
14652     return 0;
14653 }
14654
14655 void
14656 TwoMachinesEvent P((void))
14657 {
14658     int i;
14659     char buf[MSG_SIZ];
14660     ChessProgramState *onmove;
14661     char *bookHit = NULL;
14662     static int stalling = 0;
14663     TimeMark now;
14664     long wait;
14665
14666     if (appData.noChessProgram) return;
14667
14668     switch (gameMode) {
14669       case TwoMachinesPlay:
14670         return;
14671       case MachinePlaysWhite:
14672       case MachinePlaysBlack:
14673         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14674             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14675             return;
14676         }
14677         /* fall through */
14678       case BeginningOfGame:
14679       case PlayFromGameFile:
14680       case EndOfGame:
14681         EditGameEvent();
14682         if (gameMode != EditGame) return;
14683         break;
14684       case EditPosition:
14685         EditPositionDone(TRUE);
14686         break;
14687       case AnalyzeMode:
14688       case AnalyzeFile:
14689         ExitAnalyzeMode();
14690         break;
14691       case EditGame:
14692       default:
14693         break;
14694     }
14695
14696 //    forwardMostMove = currentMove;
14697     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14698     startingEngine = TRUE;
14699
14700     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14701
14702     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14703     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14704       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14705       return;
14706     }
14707     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14708
14709     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14710                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14711         startingEngine = matchMode = FALSE;
14712         DisplayError("second engine does not play this", 0);
14713         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14714         EditGameEvent(); // switch back to EditGame mode
14715         return;
14716     }
14717
14718     if(!stalling) {
14719       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14720       SendToProgram("force\n", &second);
14721       stalling = 1;
14722       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14723       return;
14724     }
14725     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14726     if(appData.matchPause>10000 || appData.matchPause<10)
14727                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14728     wait = SubtractTimeMarks(&now, &pauseStart);
14729     if(wait < appData.matchPause) {
14730         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14731         return;
14732     }
14733     // we are now committed to starting the game
14734     stalling = 0;
14735     DisplayMessage("", "");
14736     if (startedFromSetupPosition) {
14737         SendBoard(&second, backwardMostMove);
14738     if (appData.debugMode) {
14739         fprintf(debugFP, "Two Machines\n");
14740     }
14741     }
14742     for (i = backwardMostMove; i < forwardMostMove; i++) {
14743         SendMoveToProgram(i, &second);
14744     }
14745
14746     gameMode = TwoMachinesPlay;
14747     pausing = startingEngine = FALSE;
14748     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14749     SetGameInfo();
14750     DisplayTwoMachinesTitle();
14751     firstMove = TRUE;
14752     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14753         onmove = &first;
14754     } else {
14755         onmove = &second;
14756     }
14757     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14758     SendToProgram(first.computerString, &first);
14759     if (first.sendName) {
14760       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14761       SendToProgram(buf, &first);
14762     }
14763     SendToProgram(second.computerString, &second);
14764     if (second.sendName) {
14765       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14766       SendToProgram(buf, &second);
14767     }
14768
14769     ResetClocks();
14770     if (!first.sendTime || !second.sendTime) {
14771         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14772         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14773     }
14774     if (onmove->sendTime) {
14775       if (onmove->useColors) {
14776         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14777       }
14778       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14779     }
14780     if (onmove->useColors) {
14781       SendToProgram(onmove->twoMachinesColor, onmove);
14782     }
14783     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14784 //    SendToProgram("go\n", onmove);
14785     onmove->maybeThinking = TRUE;
14786     SetMachineThinkingEnables();
14787
14788     StartClocks();
14789
14790     if(bookHit) { // [HGM] book: simulate book reply
14791         static char bookMove[MSG_SIZ]; // a bit generous?
14792
14793         programStats.nodes = programStats.depth = programStats.time =
14794         programStats.score = programStats.got_only_move = 0;
14795         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14796
14797         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14798         strcat(bookMove, bookHit);
14799         savedMessage = bookMove; // args for deferred call
14800         savedState = onmove;
14801         ScheduleDelayedEvent(DeferredBookMove, 1);
14802     }
14803 }
14804
14805 void
14806 TrainingEvent ()
14807 {
14808     if (gameMode == Training) {
14809       SetTrainingModeOff();
14810       gameMode = PlayFromGameFile;
14811       DisplayMessage("", _("Training mode off"));
14812     } else {
14813       gameMode = Training;
14814       animateTraining = appData.animate;
14815
14816       /* make sure we are not already at the end of the game */
14817       if (currentMove < forwardMostMove) {
14818         SetTrainingModeOn();
14819         DisplayMessage("", _("Training mode on"));
14820       } else {
14821         gameMode = PlayFromGameFile;
14822         DisplayError(_("Already at end of game"), 0);
14823       }
14824     }
14825     ModeHighlight();
14826 }
14827
14828 void
14829 IcsClientEvent ()
14830 {
14831     if (!appData.icsActive) return;
14832     switch (gameMode) {
14833       case IcsPlayingWhite:
14834       case IcsPlayingBlack:
14835       case IcsObserving:
14836       case IcsIdle:
14837       case BeginningOfGame:
14838       case IcsExamining:
14839         return;
14840
14841       case EditGame:
14842         break;
14843
14844       case EditPosition:
14845         EditPositionDone(TRUE);
14846         break;
14847
14848       case AnalyzeMode:
14849       case AnalyzeFile:
14850         ExitAnalyzeMode();
14851         break;
14852
14853       default:
14854         EditGameEvent();
14855         break;
14856     }
14857
14858     gameMode = IcsIdle;
14859     ModeHighlight();
14860     return;
14861 }
14862
14863 void
14864 EditGameEvent ()
14865 {
14866     int i;
14867
14868     switch (gameMode) {
14869       case Training:
14870         SetTrainingModeOff();
14871         break;
14872       case MachinePlaysWhite:
14873       case MachinePlaysBlack:
14874       case BeginningOfGame:
14875         SendToProgram("force\n", &first);
14876         SetUserThinkingEnables();
14877         break;
14878       case PlayFromGameFile:
14879         (void) StopLoadGameTimer();
14880         if (gameFileFP != NULL) {
14881             gameFileFP = NULL;
14882         }
14883         break;
14884       case EditPosition:
14885         EditPositionDone(TRUE);
14886         break;
14887       case AnalyzeMode:
14888       case AnalyzeFile:
14889         ExitAnalyzeMode();
14890         SendToProgram("force\n", &first);
14891         break;
14892       case TwoMachinesPlay:
14893         GameEnds(EndOfFile, NULL, GE_PLAYER);
14894         ResurrectChessProgram();
14895         SetUserThinkingEnables();
14896         break;
14897       case EndOfGame:
14898         ResurrectChessProgram();
14899         break;
14900       case IcsPlayingBlack:
14901       case IcsPlayingWhite:
14902         DisplayError(_("Warning: You are still playing a game"), 0);
14903         break;
14904       case IcsObserving:
14905         DisplayError(_("Warning: You are still observing a game"), 0);
14906         break;
14907       case IcsExamining:
14908         DisplayError(_("Warning: You are still examining a game"), 0);
14909         break;
14910       case IcsIdle:
14911         break;
14912       case EditGame:
14913       default:
14914         return;
14915     }
14916
14917     pausing = FALSE;
14918     StopClocks();
14919     first.offeredDraw = second.offeredDraw = 0;
14920
14921     if (gameMode == PlayFromGameFile) {
14922         whiteTimeRemaining = timeRemaining[0][currentMove];
14923         blackTimeRemaining = timeRemaining[1][currentMove];
14924         DisplayTitle("");
14925     }
14926
14927     if (gameMode == MachinePlaysWhite ||
14928         gameMode == MachinePlaysBlack ||
14929         gameMode == TwoMachinesPlay ||
14930         gameMode == EndOfGame) {
14931         i = forwardMostMove;
14932         while (i > currentMove) {
14933             SendToProgram("undo\n", &first);
14934             i--;
14935         }
14936         if(!adjustedClock) {
14937         whiteTimeRemaining = timeRemaining[0][currentMove];
14938         blackTimeRemaining = timeRemaining[1][currentMove];
14939         DisplayBothClocks();
14940         }
14941         if (whiteFlag || blackFlag) {
14942             whiteFlag = blackFlag = 0;
14943         }
14944         DisplayTitle("");
14945     }
14946
14947     gameMode = EditGame;
14948     ModeHighlight();
14949     SetGameInfo();
14950 }
14951
14952
14953 void
14954 EditPositionEvent ()
14955 {
14956     if (gameMode == EditPosition) {
14957         EditGameEvent();
14958         return;
14959     }
14960
14961     EditGameEvent();
14962     if (gameMode != EditGame) return;
14963
14964     gameMode = EditPosition;
14965     ModeHighlight();
14966     SetGameInfo();
14967     if (currentMove > 0)
14968       CopyBoard(boards[0], boards[currentMove]);
14969
14970     blackPlaysFirst = !WhiteOnMove(currentMove);
14971     ResetClocks();
14972     currentMove = forwardMostMove = backwardMostMove = 0;
14973     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14974     DisplayMove(-1);
14975     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14976 }
14977
14978 void
14979 ExitAnalyzeMode ()
14980 {
14981     /* [DM] icsEngineAnalyze - possible call from other functions */
14982     if (appData.icsEngineAnalyze) {
14983         appData.icsEngineAnalyze = FALSE;
14984
14985         DisplayMessage("",_("Close ICS engine analyze..."));
14986     }
14987     if (first.analysisSupport && first.analyzing) {
14988       SendToBoth("exit\n");
14989       first.analyzing = second.analyzing = FALSE;
14990     }
14991     thinkOutput[0] = NULLCHAR;
14992 }
14993
14994 void
14995 EditPositionDone (Boolean fakeRights)
14996 {
14997     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14998
14999     startedFromSetupPosition = TRUE;
15000     InitChessProgram(&first, FALSE);
15001     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15002       boards[0][EP_STATUS] = EP_NONE;
15003       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15004       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15005         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15006         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15007       } else boards[0][CASTLING][2] = NoRights;
15008       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15009         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15010         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15011       } else boards[0][CASTLING][5] = NoRights;
15012       if(gameInfo.variant == VariantSChess) {
15013         int i;
15014         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15015           boards[0][VIRGIN][i] = 0;
15016           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15017           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15018         }
15019       }
15020     }
15021     SendToProgram("force\n", &first);
15022     if (blackPlaysFirst) {
15023         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15024         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15025         currentMove = forwardMostMove = backwardMostMove = 1;
15026         CopyBoard(boards[1], boards[0]);
15027     } else {
15028         currentMove = forwardMostMove = backwardMostMove = 0;
15029     }
15030     SendBoard(&first, forwardMostMove);
15031     if (appData.debugMode) {
15032         fprintf(debugFP, "EditPosDone\n");
15033     }
15034     DisplayTitle("");
15035     DisplayMessage("", "");
15036     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15037     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15038     gameMode = EditGame;
15039     ModeHighlight();
15040     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15041     ClearHighlights(); /* [AS] */
15042 }
15043
15044 /* Pause for `ms' milliseconds */
15045 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15046 void
15047 TimeDelay (long ms)
15048 {
15049     TimeMark m1, m2;
15050
15051     GetTimeMark(&m1);
15052     do {
15053         GetTimeMark(&m2);
15054     } while (SubtractTimeMarks(&m2, &m1) < ms);
15055 }
15056
15057 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15058 void
15059 SendMultiLineToICS (char *buf)
15060 {
15061     char temp[MSG_SIZ+1], *p;
15062     int len;
15063
15064     len = strlen(buf);
15065     if (len > MSG_SIZ)
15066       len = MSG_SIZ;
15067
15068     strncpy(temp, buf, len);
15069     temp[len] = 0;
15070
15071     p = temp;
15072     while (*p) {
15073         if (*p == '\n' || *p == '\r')
15074           *p = ' ';
15075         ++p;
15076     }
15077
15078     strcat(temp, "\n");
15079     SendToICS(temp);
15080     SendToPlayer(temp, strlen(temp));
15081 }
15082
15083 void
15084 SetWhiteToPlayEvent ()
15085 {
15086     if (gameMode == EditPosition) {
15087         blackPlaysFirst = FALSE;
15088         DisplayBothClocks();    /* works because currentMove is 0 */
15089     } else if (gameMode == IcsExamining) {
15090         SendToICS(ics_prefix);
15091         SendToICS("tomove white\n");
15092     }
15093 }
15094
15095 void
15096 SetBlackToPlayEvent ()
15097 {
15098     if (gameMode == EditPosition) {
15099         blackPlaysFirst = TRUE;
15100         currentMove = 1;        /* kludge */
15101         DisplayBothClocks();
15102         currentMove = 0;
15103     } else if (gameMode == IcsExamining) {
15104         SendToICS(ics_prefix);
15105         SendToICS("tomove black\n");
15106     }
15107 }
15108
15109 void
15110 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15111 {
15112     char buf[MSG_SIZ];
15113     ChessSquare piece = boards[0][y][x];
15114     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15115     static int lastVariant;
15116
15117     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15118
15119     switch (selection) {
15120       case ClearBoard:
15121         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15122         MarkTargetSquares(1);
15123         CopyBoard(currentBoard, boards[0]);
15124         CopyBoard(menuBoard, initialPosition);
15125         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15126             SendToICS(ics_prefix);
15127             SendToICS("bsetup clear\n");
15128         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15129             SendToICS(ics_prefix);
15130             SendToICS("clearboard\n");
15131         } else {
15132             int nonEmpty = 0;
15133             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15134                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15135                 for (y = 0; y < BOARD_HEIGHT; y++) {
15136                     if (gameMode == IcsExamining) {
15137                         if (boards[currentMove][y][x] != EmptySquare) {
15138                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15139                                     AAA + x, ONE + y);
15140                             SendToICS(buf);
15141                         }
15142                     } else if(boards[0][y][x] != DarkSquare) {
15143                         if(boards[0][y][x] != p) nonEmpty++;
15144                         boards[0][y][x] = p;
15145                     }
15146                 }
15147             }
15148             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15149                 int r;
15150                 for(r = 0; r < BOARD_HEIGHT; r++) {
15151                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15152                     ChessSquare p = menuBoard[r][x];
15153                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15154                   }
15155                 }
15156                 DisplayMessage("Clicking clock again restores position", "");
15157                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15158                 if(!nonEmpty) { // asked to clear an empty board
15159                     CopyBoard(boards[0], menuBoard);
15160                 } else
15161                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15162                     CopyBoard(boards[0], initialPosition);
15163                 } else
15164                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15165                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15166                     CopyBoard(boards[0], erasedBoard);
15167                 } else
15168                     CopyBoard(erasedBoard, currentBoard);
15169
15170             }
15171         }
15172         if (gameMode == EditPosition) {
15173             DrawPosition(FALSE, boards[0]);
15174         }
15175         break;
15176
15177       case WhitePlay:
15178         SetWhiteToPlayEvent();
15179         break;
15180
15181       case BlackPlay:
15182         SetBlackToPlayEvent();
15183         break;
15184
15185       case EmptySquare:
15186         if (gameMode == IcsExamining) {
15187             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15188             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15189             SendToICS(buf);
15190         } else {
15191             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15192                 if(x == BOARD_LEFT-2) {
15193                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15194                     boards[0][y][1] = 0;
15195                 } else
15196                 if(x == BOARD_RGHT+1) {
15197                     if(y >= gameInfo.holdingsSize) break;
15198                     boards[0][y][BOARD_WIDTH-2] = 0;
15199                 } else break;
15200             }
15201             boards[0][y][x] = EmptySquare;
15202             DrawPosition(FALSE, boards[0]);
15203         }
15204         break;
15205
15206       case PromotePiece:
15207         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15208            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15209             selection = (ChessSquare) (PROMOTED piece);
15210         } else if(piece == EmptySquare) selection = WhiteSilver;
15211         else selection = (ChessSquare)((int)piece - 1);
15212         goto defaultlabel;
15213
15214       case DemotePiece:
15215         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15216            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15217             selection = (ChessSquare) (DEMOTED piece);
15218         } else if(piece == EmptySquare) selection = BlackSilver;
15219         else selection = (ChessSquare)((int)piece + 1);
15220         goto defaultlabel;
15221
15222       case WhiteQueen:
15223       case BlackQueen:
15224         if(gameInfo.variant == VariantShatranj ||
15225            gameInfo.variant == VariantXiangqi  ||
15226            gameInfo.variant == VariantCourier  ||
15227            gameInfo.variant == VariantASEAN    ||
15228            gameInfo.variant == VariantMakruk     )
15229             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15230         goto defaultlabel;
15231
15232       case WhiteKing:
15233       case BlackKing:
15234         if(gameInfo.variant == VariantXiangqi)
15235             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15236         if(gameInfo.variant == VariantKnightmate)
15237             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15238       default:
15239         defaultlabel:
15240         if (gameMode == IcsExamining) {
15241             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15242             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15243                      PieceToChar(selection), AAA + x, ONE + y);
15244             SendToICS(buf);
15245         } else {
15246             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15247                 int n;
15248                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15249                     n = PieceToNumber(selection - BlackPawn);
15250                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15251                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15252                     boards[0][BOARD_HEIGHT-1-n][1]++;
15253                 } else
15254                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15255                     n = PieceToNumber(selection);
15256                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15257                     boards[0][n][BOARD_WIDTH-1] = selection;
15258                     boards[0][n][BOARD_WIDTH-2]++;
15259                 }
15260             } else
15261             boards[0][y][x] = selection;
15262             DrawPosition(TRUE, boards[0]);
15263             ClearHighlights();
15264             fromX = fromY = -1;
15265         }
15266         break;
15267     }
15268 }
15269
15270
15271 void
15272 DropMenuEvent (ChessSquare selection, int x, int y)
15273 {
15274     ChessMove moveType;
15275
15276     switch (gameMode) {
15277       case IcsPlayingWhite:
15278       case MachinePlaysBlack:
15279         if (!WhiteOnMove(currentMove)) {
15280             DisplayMoveError(_("It is Black's turn"));
15281             return;
15282         }
15283         moveType = WhiteDrop;
15284         break;
15285       case IcsPlayingBlack:
15286       case MachinePlaysWhite:
15287         if (WhiteOnMove(currentMove)) {
15288             DisplayMoveError(_("It is White's turn"));
15289             return;
15290         }
15291         moveType = BlackDrop;
15292         break;
15293       case EditGame:
15294         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15295         break;
15296       default:
15297         return;
15298     }
15299
15300     if (moveType == BlackDrop && selection < BlackPawn) {
15301       selection = (ChessSquare) ((int) selection
15302                                  + (int) BlackPawn - (int) WhitePawn);
15303     }
15304     if (boards[currentMove][y][x] != EmptySquare) {
15305         DisplayMoveError(_("That square is occupied"));
15306         return;
15307     }
15308
15309     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15310 }
15311
15312 void
15313 AcceptEvent ()
15314 {
15315     /* Accept a pending offer of any kind from opponent */
15316
15317     if (appData.icsActive) {
15318         SendToICS(ics_prefix);
15319         SendToICS("accept\n");
15320     } else if (cmailMsgLoaded) {
15321         if (currentMove == cmailOldMove &&
15322             commentList[cmailOldMove] != NULL &&
15323             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15324                    "Black offers a draw" : "White offers a draw")) {
15325             TruncateGame();
15326             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15327             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15328         } else {
15329             DisplayError(_("There is no pending offer on this move"), 0);
15330             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15331         }
15332     } else {
15333         /* Not used for offers from chess program */
15334     }
15335 }
15336
15337 void
15338 DeclineEvent ()
15339 {
15340     /* Decline a pending offer of any kind from opponent */
15341
15342     if (appData.icsActive) {
15343         SendToICS(ics_prefix);
15344         SendToICS("decline\n");
15345     } else if (cmailMsgLoaded) {
15346         if (currentMove == cmailOldMove &&
15347             commentList[cmailOldMove] != NULL &&
15348             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15349                    "Black offers a draw" : "White offers a draw")) {
15350 #ifdef NOTDEF
15351             AppendComment(cmailOldMove, "Draw declined", TRUE);
15352             DisplayComment(cmailOldMove - 1, "Draw declined");
15353 #endif /*NOTDEF*/
15354         } else {
15355             DisplayError(_("There is no pending offer on this move"), 0);
15356         }
15357     } else {
15358         /* Not used for offers from chess program */
15359     }
15360 }
15361
15362 void
15363 RematchEvent ()
15364 {
15365     /* Issue ICS rematch command */
15366     if (appData.icsActive) {
15367         SendToICS(ics_prefix);
15368         SendToICS("rematch\n");
15369     }
15370 }
15371
15372 void
15373 CallFlagEvent ()
15374 {
15375     /* Call your opponent's flag (claim a win on time) */
15376     if (appData.icsActive) {
15377         SendToICS(ics_prefix);
15378         SendToICS("flag\n");
15379     } else {
15380         switch (gameMode) {
15381           default:
15382             return;
15383           case MachinePlaysWhite:
15384             if (whiteFlag) {
15385                 if (blackFlag)
15386                   GameEnds(GameIsDrawn, "Both players ran out of time",
15387                            GE_PLAYER);
15388                 else
15389                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15390             } else {
15391                 DisplayError(_("Your opponent is not out of time"), 0);
15392             }
15393             break;
15394           case MachinePlaysBlack:
15395             if (blackFlag) {
15396                 if (whiteFlag)
15397                   GameEnds(GameIsDrawn, "Both players ran out of time",
15398                            GE_PLAYER);
15399                 else
15400                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15401             } else {
15402                 DisplayError(_("Your opponent is not out of time"), 0);
15403             }
15404             break;
15405         }
15406     }
15407 }
15408
15409 void
15410 ClockClick (int which)
15411 {       // [HGM] code moved to back-end from winboard.c
15412         if(which) { // black clock
15413           if (gameMode == EditPosition || gameMode == IcsExamining) {
15414             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15415             SetBlackToPlayEvent();
15416           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15417                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15418           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15419           } else if (shiftKey) {
15420             AdjustClock(which, -1);
15421           } else if (gameMode == IcsPlayingWhite ||
15422                      gameMode == MachinePlaysBlack) {
15423             CallFlagEvent();
15424           }
15425         } else { // white clock
15426           if (gameMode == EditPosition || gameMode == IcsExamining) {
15427             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15428             SetWhiteToPlayEvent();
15429           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15430                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15431           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15432           } else if (shiftKey) {
15433             AdjustClock(which, -1);
15434           } else if (gameMode == IcsPlayingBlack ||
15435                    gameMode == MachinePlaysWhite) {
15436             CallFlagEvent();
15437           }
15438         }
15439 }
15440
15441 void
15442 DrawEvent ()
15443 {
15444     /* Offer draw or accept pending draw offer from opponent */
15445
15446     if (appData.icsActive) {
15447         /* Note: tournament rules require draw offers to be
15448            made after you make your move but before you punch
15449            your clock.  Currently ICS doesn't let you do that;
15450            instead, you immediately punch your clock after making
15451            a move, but you can offer a draw at any time. */
15452
15453         SendToICS(ics_prefix);
15454         SendToICS("draw\n");
15455         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15456     } else if (cmailMsgLoaded) {
15457         if (currentMove == cmailOldMove &&
15458             commentList[cmailOldMove] != NULL &&
15459             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15460                    "Black offers a draw" : "White offers a draw")) {
15461             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15462             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15463         } else if (currentMove == cmailOldMove + 1) {
15464             char *offer = WhiteOnMove(cmailOldMove) ?
15465               "White offers a draw" : "Black offers a draw";
15466             AppendComment(currentMove, offer, TRUE);
15467             DisplayComment(currentMove - 1, offer);
15468             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15469         } else {
15470             DisplayError(_("You must make your move before offering a draw"), 0);
15471             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15472         }
15473     } else if (first.offeredDraw) {
15474         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15475     } else {
15476         if (first.sendDrawOffers) {
15477             SendToProgram("draw\n", &first);
15478             userOfferedDraw = TRUE;
15479         }
15480     }
15481 }
15482
15483 void
15484 AdjournEvent ()
15485 {
15486     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15487
15488     if (appData.icsActive) {
15489         SendToICS(ics_prefix);
15490         SendToICS("adjourn\n");
15491     } else {
15492         /* Currently GNU Chess doesn't offer or accept Adjourns */
15493     }
15494 }
15495
15496
15497 void
15498 AbortEvent ()
15499 {
15500     /* Offer Abort or accept pending Abort offer from opponent */
15501
15502     if (appData.icsActive) {
15503         SendToICS(ics_prefix);
15504         SendToICS("abort\n");
15505     } else {
15506         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15507     }
15508 }
15509
15510 void
15511 ResignEvent ()
15512 {
15513     /* Resign.  You can do this even if it's not your turn. */
15514
15515     if (appData.icsActive) {
15516         SendToICS(ics_prefix);
15517         SendToICS("resign\n");
15518     } else {
15519         switch (gameMode) {
15520           case MachinePlaysWhite:
15521             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15522             break;
15523           case MachinePlaysBlack:
15524             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15525             break;
15526           case EditGame:
15527             if (cmailMsgLoaded) {
15528                 TruncateGame();
15529                 if (WhiteOnMove(cmailOldMove)) {
15530                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15531                 } else {
15532                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15533                 }
15534                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15535             }
15536             break;
15537           default:
15538             break;
15539         }
15540     }
15541 }
15542
15543
15544 void
15545 StopObservingEvent ()
15546 {
15547     /* Stop observing current games */
15548     SendToICS(ics_prefix);
15549     SendToICS("unobserve\n");
15550 }
15551
15552 void
15553 StopExaminingEvent ()
15554 {
15555     /* Stop observing current game */
15556     SendToICS(ics_prefix);
15557     SendToICS("unexamine\n");
15558 }
15559
15560 void
15561 ForwardInner (int target)
15562 {
15563     int limit; int oldSeekGraphUp = seekGraphUp;
15564
15565     if (appData.debugMode)
15566         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15567                 target, currentMove, forwardMostMove);
15568
15569     if (gameMode == EditPosition)
15570       return;
15571
15572     seekGraphUp = FALSE;
15573     MarkTargetSquares(1);
15574     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15575
15576     if (gameMode == PlayFromGameFile && !pausing)
15577       PauseEvent();
15578
15579     if (gameMode == IcsExamining && pausing)
15580       limit = pauseExamForwardMostMove;
15581     else
15582       limit = forwardMostMove;
15583
15584     if (target > limit) target = limit;
15585
15586     if (target > 0 && moveList[target - 1][0]) {
15587         int fromX, fromY, toX, toY;
15588         toX = moveList[target - 1][2] - AAA;
15589         toY = moveList[target - 1][3] - ONE;
15590         if (moveList[target - 1][1] == '@') {
15591             if (appData.highlightLastMove) {
15592                 SetHighlights(-1, -1, toX, toY);
15593             }
15594         } else {
15595             int viaX = moveList[target - 1][5] - AAA;
15596             int viaY = moveList[target - 1][6] - ONE;
15597             fromX = moveList[target - 1][0] - AAA;
15598             fromY = moveList[target - 1][1] - ONE;
15599             if (target == currentMove + 1) {
15600                 if(moveList[target - 1][4] == ';') { // multi-leg
15601                     ChessSquare piece = boards[currentMove][viaY][viaX];
15602                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15603                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15604                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15605                     boards[currentMove][viaY][viaX] = piece;
15606                 } else
15607                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15608             }
15609             if (appData.highlightLastMove) {
15610                 SetHighlights(fromX, fromY, toX, toY);
15611             }
15612         }
15613     }
15614     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15615         gameMode == Training || gameMode == PlayFromGameFile ||
15616         gameMode == AnalyzeFile) {
15617         while (currentMove < target) {
15618             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15619             SendMoveToProgram(currentMove++, &first);
15620         }
15621     } else {
15622         currentMove = target;
15623     }
15624
15625     if (gameMode == EditGame || gameMode == EndOfGame) {
15626         whiteTimeRemaining = timeRemaining[0][currentMove];
15627         blackTimeRemaining = timeRemaining[1][currentMove];
15628     }
15629     DisplayBothClocks();
15630     DisplayMove(currentMove - 1);
15631     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15632     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15633     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15634         DisplayComment(currentMove - 1, commentList[currentMove]);
15635     }
15636     ClearMap(); // [HGM] exclude: invalidate map
15637 }
15638
15639
15640 void
15641 ForwardEvent ()
15642 {
15643     if (gameMode == IcsExamining && !pausing) {
15644         SendToICS(ics_prefix);
15645         SendToICS("forward\n");
15646     } else {
15647         ForwardInner(currentMove + 1);
15648     }
15649 }
15650
15651 void
15652 ToEndEvent ()
15653 {
15654     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15655         /* to optimze, we temporarily turn off analysis mode while we feed
15656          * the remaining moves to the engine. Otherwise we get analysis output
15657          * after each move.
15658          */
15659         if (first.analysisSupport) {
15660           SendToProgram("exit\nforce\n", &first);
15661           first.analyzing = FALSE;
15662         }
15663     }
15664
15665     if (gameMode == IcsExamining && !pausing) {
15666         SendToICS(ics_prefix);
15667         SendToICS("forward 999999\n");
15668     } else {
15669         ForwardInner(forwardMostMove);
15670     }
15671
15672     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15673         /* we have fed all the moves, so reactivate analysis mode */
15674         SendToProgram("analyze\n", &first);
15675         first.analyzing = TRUE;
15676         /*first.maybeThinking = TRUE;*/
15677         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15678     }
15679 }
15680
15681 void
15682 BackwardInner (int target)
15683 {
15684     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15685
15686     if (appData.debugMode)
15687         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15688                 target, currentMove, forwardMostMove);
15689
15690     if (gameMode == EditPosition) return;
15691     seekGraphUp = FALSE;
15692     MarkTargetSquares(1);
15693     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15694     if (currentMove <= backwardMostMove) {
15695         ClearHighlights();
15696         DrawPosition(full_redraw, boards[currentMove]);
15697         return;
15698     }
15699     if (gameMode == PlayFromGameFile && !pausing)
15700       PauseEvent();
15701
15702     if (moveList[target][0]) {
15703         int fromX, fromY, toX, toY;
15704         toX = moveList[target][2] - AAA;
15705         toY = moveList[target][3] - ONE;
15706         if (moveList[target][1] == '@') {
15707             if (appData.highlightLastMove) {
15708                 SetHighlights(-1, -1, toX, toY);
15709             }
15710         } else {
15711             fromX = moveList[target][0] - AAA;
15712             fromY = moveList[target][1] - ONE;
15713             if (target == currentMove - 1) {
15714                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15715             }
15716             if (appData.highlightLastMove) {
15717                 SetHighlights(fromX, fromY, toX, toY);
15718             }
15719         }
15720     }
15721     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15722         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15723         while (currentMove > target) {
15724             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15725                 // null move cannot be undone. Reload program with move history before it.
15726                 int i;
15727                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15728                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15729                 }
15730                 SendBoard(&first, i);
15731               if(second.analyzing) SendBoard(&second, i);
15732                 for(currentMove=i; currentMove<target; currentMove++) {
15733                     SendMoveToProgram(currentMove, &first);
15734                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15735                 }
15736                 break;
15737             }
15738             SendToBoth("undo\n");
15739             currentMove--;
15740         }
15741     } else {
15742         currentMove = target;
15743     }
15744
15745     if (gameMode == EditGame || gameMode == EndOfGame) {
15746         whiteTimeRemaining = timeRemaining[0][currentMove];
15747         blackTimeRemaining = timeRemaining[1][currentMove];
15748     }
15749     DisplayBothClocks();
15750     DisplayMove(currentMove - 1);
15751     DrawPosition(full_redraw, boards[currentMove]);
15752     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15753     // [HGM] PV info: routine tests if comment empty
15754     DisplayComment(currentMove - 1, commentList[currentMove]);
15755     ClearMap(); // [HGM] exclude: invalidate map
15756 }
15757
15758 void
15759 BackwardEvent ()
15760 {
15761     if (gameMode == IcsExamining && !pausing) {
15762         SendToICS(ics_prefix);
15763         SendToICS("backward\n");
15764     } else {
15765         BackwardInner(currentMove - 1);
15766     }
15767 }
15768
15769 void
15770 ToStartEvent ()
15771 {
15772     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15773         /* to optimize, we temporarily turn off analysis mode while we undo
15774          * all the moves. Otherwise we get analysis output after each undo.
15775          */
15776         if (first.analysisSupport) {
15777           SendToProgram("exit\nforce\n", &first);
15778           first.analyzing = FALSE;
15779         }
15780     }
15781
15782     if (gameMode == IcsExamining && !pausing) {
15783         SendToICS(ics_prefix);
15784         SendToICS("backward 999999\n");
15785     } else {
15786         BackwardInner(backwardMostMove);
15787     }
15788
15789     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15790         /* we have fed all the moves, so reactivate analysis mode */
15791         SendToProgram("analyze\n", &first);
15792         first.analyzing = TRUE;
15793         /*first.maybeThinking = TRUE;*/
15794         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15795     }
15796 }
15797
15798 void
15799 ToNrEvent (int to)
15800 {
15801   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15802   if (to >= forwardMostMove) to = forwardMostMove;
15803   if (to <= backwardMostMove) to = backwardMostMove;
15804   if (to < currentMove) {
15805     BackwardInner(to);
15806   } else {
15807     ForwardInner(to);
15808   }
15809 }
15810
15811 void
15812 RevertEvent (Boolean annotate)
15813 {
15814     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15815         return;
15816     }
15817     if (gameMode != IcsExamining) {
15818         DisplayError(_("You are not examining a game"), 0);
15819         return;
15820     }
15821     if (pausing) {
15822         DisplayError(_("You can't revert while pausing"), 0);
15823         return;
15824     }
15825     SendToICS(ics_prefix);
15826     SendToICS("revert\n");
15827 }
15828
15829 void
15830 RetractMoveEvent ()
15831 {
15832     switch (gameMode) {
15833       case MachinePlaysWhite:
15834       case MachinePlaysBlack:
15835         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15836             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15837             return;
15838         }
15839         if (forwardMostMove < 2) return;
15840         currentMove = forwardMostMove = forwardMostMove - 2;
15841         whiteTimeRemaining = timeRemaining[0][currentMove];
15842         blackTimeRemaining = timeRemaining[1][currentMove];
15843         DisplayBothClocks();
15844         DisplayMove(currentMove - 1);
15845         ClearHighlights();/*!! could figure this out*/
15846         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15847         SendToProgram("remove\n", &first);
15848         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15849         break;
15850
15851       case BeginningOfGame:
15852       default:
15853         break;
15854
15855       case IcsPlayingWhite:
15856       case IcsPlayingBlack:
15857         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15858             SendToICS(ics_prefix);
15859             SendToICS("takeback 2\n");
15860         } else {
15861             SendToICS(ics_prefix);
15862             SendToICS("takeback 1\n");
15863         }
15864         break;
15865     }
15866 }
15867
15868 void
15869 MoveNowEvent ()
15870 {
15871     ChessProgramState *cps;
15872
15873     switch (gameMode) {
15874       case MachinePlaysWhite:
15875         if (!WhiteOnMove(forwardMostMove)) {
15876             DisplayError(_("It is your turn"), 0);
15877             return;
15878         }
15879         cps = &first;
15880         break;
15881       case MachinePlaysBlack:
15882         if (WhiteOnMove(forwardMostMove)) {
15883             DisplayError(_("It is your turn"), 0);
15884             return;
15885         }
15886         cps = &first;
15887         break;
15888       case TwoMachinesPlay:
15889         if (WhiteOnMove(forwardMostMove) ==
15890             (first.twoMachinesColor[0] == 'w')) {
15891             cps = &first;
15892         } else {
15893             cps = &second;
15894         }
15895         break;
15896       case BeginningOfGame:
15897       default:
15898         return;
15899     }
15900     SendToProgram("?\n", cps);
15901 }
15902
15903 void
15904 TruncateGameEvent ()
15905 {
15906     EditGameEvent();
15907     if (gameMode != EditGame) return;
15908     TruncateGame();
15909 }
15910
15911 void
15912 TruncateGame ()
15913 {
15914     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15915     if (forwardMostMove > currentMove) {
15916         if (gameInfo.resultDetails != NULL) {
15917             free(gameInfo.resultDetails);
15918             gameInfo.resultDetails = NULL;
15919             gameInfo.result = GameUnfinished;
15920         }
15921         forwardMostMove = currentMove;
15922         HistorySet(parseList, backwardMostMove, forwardMostMove,
15923                    currentMove-1);
15924     }
15925 }
15926
15927 void
15928 HintEvent ()
15929 {
15930     if (appData.noChessProgram) return;
15931     switch (gameMode) {
15932       case MachinePlaysWhite:
15933         if (WhiteOnMove(forwardMostMove)) {
15934             DisplayError(_("Wait until your turn."), 0);
15935             return;
15936         }
15937         break;
15938       case BeginningOfGame:
15939       case MachinePlaysBlack:
15940         if (!WhiteOnMove(forwardMostMove)) {
15941             DisplayError(_("Wait until your turn."), 0);
15942             return;
15943         }
15944         break;
15945       default:
15946         DisplayError(_("No hint available"), 0);
15947         return;
15948     }
15949     SendToProgram("hint\n", &first);
15950     hintRequested = TRUE;
15951 }
15952
15953 int
15954 SaveSelected (FILE *g, int dummy, char *dummy2)
15955 {
15956     ListGame * lg = (ListGame *) gameList.head;
15957     int nItem, cnt=0;
15958     FILE *f;
15959
15960     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15961         DisplayError(_("Game list not loaded or empty"), 0);
15962         return 0;
15963     }
15964
15965     creatingBook = TRUE; // suppresses stuff during load game
15966
15967     /* Get list size */
15968     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15969         if(lg->position >= 0) { // selected?
15970             LoadGame(f, nItem, "", TRUE);
15971             SaveGamePGN2(g); // leaves g open
15972             cnt++; DoEvents();
15973         }
15974         lg = (ListGame *) lg->node.succ;
15975     }
15976
15977     fclose(g);
15978     creatingBook = FALSE;
15979
15980     return cnt;
15981 }
15982
15983 void
15984 CreateBookEvent ()
15985 {
15986     ListGame * lg = (ListGame *) gameList.head;
15987     FILE *f, *g;
15988     int nItem;
15989     static int secondTime = FALSE;
15990
15991     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15992         DisplayError(_("Game list not loaded or empty"), 0);
15993         return;
15994     }
15995
15996     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15997         fclose(g);
15998         secondTime++;
15999         DisplayNote(_("Book file exists! Try again for overwrite."));
16000         return;
16001     }
16002
16003     creatingBook = TRUE;
16004     secondTime = FALSE;
16005
16006     /* Get list size */
16007     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16008         if(lg->position >= 0) {
16009             LoadGame(f, nItem, "", TRUE);
16010             AddGameToBook(TRUE);
16011             DoEvents();
16012         }
16013         lg = (ListGame *) lg->node.succ;
16014     }
16015
16016     creatingBook = FALSE;
16017     FlushBook();
16018 }
16019
16020 void
16021 BookEvent ()
16022 {
16023     if (appData.noChessProgram) return;
16024     switch (gameMode) {
16025       case MachinePlaysWhite:
16026         if (WhiteOnMove(forwardMostMove)) {
16027             DisplayError(_("Wait until your turn."), 0);
16028             return;
16029         }
16030         break;
16031       case BeginningOfGame:
16032       case MachinePlaysBlack:
16033         if (!WhiteOnMove(forwardMostMove)) {
16034             DisplayError(_("Wait until your turn."), 0);
16035             return;
16036         }
16037         break;
16038       case EditPosition:
16039         EditPositionDone(TRUE);
16040         break;
16041       case TwoMachinesPlay:
16042         return;
16043       default:
16044         break;
16045     }
16046     SendToProgram("bk\n", &first);
16047     bookOutput[0] = NULLCHAR;
16048     bookRequested = TRUE;
16049 }
16050
16051 void
16052 AboutGameEvent ()
16053 {
16054     char *tags = PGNTags(&gameInfo);
16055     TagsPopUp(tags, CmailMsg());
16056     free(tags);
16057 }
16058
16059 /* end button procedures */
16060
16061 void
16062 PrintPosition (FILE *fp, int move)
16063 {
16064     int i, j;
16065
16066     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16067         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16068             char c = PieceToChar(boards[move][i][j]);
16069             fputc(c == 'x' ? '.' : c, fp);
16070             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16071         }
16072     }
16073     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16074       fprintf(fp, "white to play\n");
16075     else
16076       fprintf(fp, "black to play\n");
16077 }
16078
16079 void
16080 PrintOpponents (FILE *fp)
16081 {
16082     if (gameInfo.white != NULL) {
16083         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16084     } else {
16085         fprintf(fp, "\n");
16086     }
16087 }
16088
16089 /* Find last component of program's own name, using some heuristics */
16090 void
16091 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16092 {
16093     char *p, *q, c;
16094     int local = (strcmp(host, "localhost") == 0);
16095     while (!local && (p = strchr(prog, ';')) != NULL) {
16096         p++;
16097         while (*p == ' ') p++;
16098         prog = p;
16099     }
16100     if (*prog == '"' || *prog == '\'') {
16101         q = strchr(prog + 1, *prog);
16102     } else {
16103         q = strchr(prog, ' ');
16104     }
16105     if (q == NULL) q = prog + strlen(prog);
16106     p = q;
16107     while (p >= prog && *p != '/' && *p != '\\') p--;
16108     p++;
16109     if(p == prog && *p == '"') p++;
16110     c = *q; *q = 0;
16111     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16112     memcpy(buf, p, q - p);
16113     buf[q - p] = NULLCHAR;
16114     if (!local) {
16115         strcat(buf, "@");
16116         strcat(buf, host);
16117     }
16118 }
16119
16120 char *
16121 TimeControlTagValue ()
16122 {
16123     char buf[MSG_SIZ];
16124     if (!appData.clockMode) {
16125       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16126     } else if (movesPerSession > 0) {
16127       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16128     } else if (timeIncrement == 0) {
16129       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16130     } else {
16131       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16132     }
16133     return StrSave(buf);
16134 }
16135
16136 void
16137 SetGameInfo ()
16138 {
16139     /* This routine is used only for certain modes */
16140     VariantClass v = gameInfo.variant;
16141     ChessMove r = GameUnfinished;
16142     char *p = NULL;
16143
16144     if(keepInfo) return;
16145
16146     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16147         r = gameInfo.result;
16148         p = gameInfo.resultDetails;
16149         gameInfo.resultDetails = NULL;
16150     }
16151     ClearGameInfo(&gameInfo);
16152     gameInfo.variant = v;
16153
16154     switch (gameMode) {
16155       case MachinePlaysWhite:
16156         gameInfo.event = StrSave( appData.pgnEventHeader );
16157         gameInfo.site = StrSave(HostName());
16158         gameInfo.date = PGNDate();
16159         gameInfo.round = StrSave("-");
16160         gameInfo.white = StrSave(first.tidy);
16161         gameInfo.black = StrSave(UserName());
16162         gameInfo.timeControl = TimeControlTagValue();
16163         break;
16164
16165       case MachinePlaysBlack:
16166         gameInfo.event = StrSave( appData.pgnEventHeader );
16167         gameInfo.site = StrSave(HostName());
16168         gameInfo.date = PGNDate();
16169         gameInfo.round = StrSave("-");
16170         gameInfo.white = StrSave(UserName());
16171         gameInfo.black = StrSave(first.tidy);
16172         gameInfo.timeControl = TimeControlTagValue();
16173         break;
16174
16175       case TwoMachinesPlay:
16176         gameInfo.event = StrSave( appData.pgnEventHeader );
16177         gameInfo.site = StrSave(HostName());
16178         gameInfo.date = PGNDate();
16179         if (roundNr > 0) {
16180             char buf[MSG_SIZ];
16181             snprintf(buf, MSG_SIZ, "%d", roundNr);
16182             gameInfo.round = StrSave(buf);
16183         } else {
16184             gameInfo.round = StrSave("-");
16185         }
16186         if (first.twoMachinesColor[0] == 'w') {
16187             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16188             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16189         } else {
16190             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16191             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16192         }
16193         gameInfo.timeControl = TimeControlTagValue();
16194         break;
16195
16196       case EditGame:
16197         gameInfo.event = StrSave("Edited game");
16198         gameInfo.site = StrSave(HostName());
16199         gameInfo.date = PGNDate();
16200         gameInfo.round = StrSave("-");
16201         gameInfo.white = StrSave("-");
16202         gameInfo.black = StrSave("-");
16203         gameInfo.result = r;
16204         gameInfo.resultDetails = p;
16205         break;
16206
16207       case EditPosition:
16208         gameInfo.event = StrSave("Edited position");
16209         gameInfo.site = StrSave(HostName());
16210         gameInfo.date = PGNDate();
16211         gameInfo.round = StrSave("-");
16212         gameInfo.white = StrSave("-");
16213         gameInfo.black = StrSave("-");
16214         break;
16215
16216       case IcsPlayingWhite:
16217       case IcsPlayingBlack:
16218       case IcsObserving:
16219       case IcsExamining:
16220         break;
16221
16222       case PlayFromGameFile:
16223         gameInfo.event = StrSave("Game from non-PGN file");
16224         gameInfo.site = StrSave(HostName());
16225         gameInfo.date = PGNDate();
16226         gameInfo.round = StrSave("-");
16227         gameInfo.white = StrSave("?");
16228         gameInfo.black = StrSave("?");
16229         break;
16230
16231       default:
16232         break;
16233     }
16234 }
16235
16236 void
16237 ReplaceComment (int index, char *text)
16238 {
16239     int len;
16240     char *p;
16241     float score;
16242
16243     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16244        pvInfoList[index-1].depth == len &&
16245        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16246        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16247     while (*text == '\n') text++;
16248     len = strlen(text);
16249     while (len > 0 && text[len - 1] == '\n') len--;
16250
16251     if (commentList[index] != NULL)
16252       free(commentList[index]);
16253
16254     if (len == 0) {
16255         commentList[index] = NULL;
16256         return;
16257     }
16258   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16259       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16260       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16261     commentList[index] = (char *) malloc(len + 2);
16262     strncpy(commentList[index], text, len);
16263     commentList[index][len] = '\n';
16264     commentList[index][len + 1] = NULLCHAR;
16265   } else {
16266     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16267     char *p;
16268     commentList[index] = (char *) malloc(len + 7);
16269     safeStrCpy(commentList[index], "{\n", 3);
16270     safeStrCpy(commentList[index]+2, text, len+1);
16271     commentList[index][len+2] = NULLCHAR;
16272     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16273     strcat(commentList[index], "\n}\n");
16274   }
16275 }
16276
16277 void
16278 CrushCRs (char *text)
16279 {
16280   char *p = text;
16281   char *q = text;
16282   char ch;
16283
16284   do {
16285     ch = *p++;
16286     if (ch == '\r') continue;
16287     *q++ = ch;
16288   } while (ch != '\0');
16289 }
16290
16291 void
16292 AppendComment (int index, char *text, Boolean addBraces)
16293 /* addBraces  tells if we should add {} */
16294 {
16295     int oldlen, len;
16296     char *old;
16297
16298 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16299     if(addBraces == 3) addBraces = 0; else // force appending literally
16300     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16301
16302     CrushCRs(text);
16303     while (*text == '\n') text++;
16304     len = strlen(text);
16305     while (len > 0 && text[len - 1] == '\n') len--;
16306     text[len] = NULLCHAR;
16307
16308     if (len == 0) return;
16309
16310     if (commentList[index] != NULL) {
16311       Boolean addClosingBrace = addBraces;
16312         old = commentList[index];
16313         oldlen = strlen(old);
16314         while(commentList[index][oldlen-1] ==  '\n')
16315           commentList[index][--oldlen] = NULLCHAR;
16316         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16317         safeStrCpy(commentList[index], old, oldlen + len + 6);
16318         free(old);
16319         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16320         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16321           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16322           while (*text == '\n') { text++; len--; }
16323           commentList[index][--oldlen] = NULLCHAR;
16324       }
16325         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16326         else          strcat(commentList[index], "\n");
16327         strcat(commentList[index], text);
16328         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16329         else          strcat(commentList[index], "\n");
16330     } else {
16331         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16332         if(addBraces)
16333           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16334         else commentList[index][0] = NULLCHAR;
16335         strcat(commentList[index], text);
16336         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16337         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16338     }
16339 }
16340
16341 static char *
16342 FindStr (char * text, char * sub_text)
16343 {
16344     char * result = strstr( text, sub_text );
16345
16346     if( result != NULL ) {
16347         result += strlen( sub_text );
16348     }
16349
16350     return result;
16351 }
16352
16353 /* [AS] Try to extract PV info from PGN comment */
16354 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16355 char *
16356 GetInfoFromComment (int index, char * text)
16357 {
16358     char * sep = text, *p;
16359
16360     if( text != NULL && index > 0 ) {
16361         int score = 0;
16362         int depth = 0;
16363         int time = -1, sec = 0, deci;
16364         char * s_eval = FindStr( text, "[%eval " );
16365         char * s_emt = FindStr( text, "[%emt " );
16366 #if 0
16367         if( s_eval != NULL || s_emt != NULL ) {
16368 #else
16369         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16370 #endif
16371             /* New style */
16372             char delim;
16373
16374             if( s_eval != NULL ) {
16375                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16376                     return text;
16377                 }
16378
16379                 if( delim != ']' ) {
16380                     return text;
16381                 }
16382             }
16383
16384             if( s_emt != NULL ) {
16385             }
16386                 return text;
16387         }
16388         else {
16389             /* We expect something like: [+|-]nnn.nn/dd */
16390             int score_lo = 0;
16391
16392             if(*text != '{') return text; // [HGM] braces: must be normal comment
16393
16394             sep = strchr( text, '/' );
16395             if( sep == NULL || sep < (text+4) ) {
16396                 return text;
16397             }
16398
16399             p = text;
16400             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16401             if(p[1] == '(') { // comment starts with PV
16402                p = strchr(p, ')'); // locate end of PV
16403                if(p == NULL || sep < p+5) return text;
16404                // at this point we have something like "{(.*) +0.23/6 ..."
16405                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16406                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16407                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16408             }
16409             time = -1; sec = -1; deci = -1;
16410             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16411                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16412                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16413                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16414                 return text;
16415             }
16416
16417             if( score_lo < 0 || score_lo >= 100 ) {
16418                 return text;
16419             }
16420
16421             if(sec >= 0) time = 600*time + 10*sec; else
16422             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16423
16424             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16425
16426             /* [HGM] PV time: now locate end of PV info */
16427             while( *++sep >= '0' && *sep <= '9'); // strip depth
16428             if(time >= 0)
16429             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16430             if(sec >= 0)
16431             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16432             if(deci >= 0)
16433             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16434             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16435         }
16436
16437         if( depth <= 0 ) {
16438             return text;
16439         }
16440
16441         if( time < 0 ) {
16442             time = -1;
16443         }
16444
16445         pvInfoList[index-1].depth = depth;
16446         pvInfoList[index-1].score = score;
16447         pvInfoList[index-1].time  = 10*time; // centi-sec
16448         if(*sep == '}') *sep = 0; else *--sep = '{';
16449         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16450     }
16451     return sep;
16452 }
16453
16454 void
16455 SendToProgram (char *message, ChessProgramState *cps)
16456 {
16457     int count, outCount, error;
16458     char buf[MSG_SIZ];
16459
16460     if (cps->pr == NoProc) return;
16461     Attention(cps);
16462
16463     if (appData.debugMode) {
16464         TimeMark now;
16465         GetTimeMark(&now);
16466         fprintf(debugFP, "%ld >%-6s: %s",
16467                 SubtractTimeMarks(&now, &programStartTime),
16468                 cps->which, message);
16469         if(serverFP)
16470             fprintf(serverFP, "%ld >%-6s: %s",
16471                 SubtractTimeMarks(&now, &programStartTime),
16472                 cps->which, message), fflush(serverFP);
16473     }
16474
16475     count = strlen(message);
16476     outCount = OutputToProcess(cps->pr, message, count, &error);
16477     if (outCount < count && !exiting
16478                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16479       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16480       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16481         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16482             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16483                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16484                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16485                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16486             } else {
16487                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16488                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16489                 gameInfo.result = res;
16490             }
16491             gameInfo.resultDetails = StrSave(buf);
16492         }
16493         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16494         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16495     }
16496 }
16497
16498 void
16499 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16500 {
16501     char *end_str;
16502     char buf[MSG_SIZ];
16503     ChessProgramState *cps = (ChessProgramState *)closure;
16504
16505     if (isr != cps->isr) return; /* Killed intentionally */
16506     if (count <= 0) {
16507         if (count == 0) {
16508             RemoveInputSource(cps->isr);
16509             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16510                     _(cps->which), cps->program);
16511             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16512             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16513                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16514                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16515                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16516                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16517                 } else {
16518                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16519                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16520                     gameInfo.result = res;
16521                 }
16522                 gameInfo.resultDetails = StrSave(buf);
16523             }
16524             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16525             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16526         } else {
16527             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16528                     _(cps->which), cps->program);
16529             RemoveInputSource(cps->isr);
16530
16531             /* [AS] Program is misbehaving badly... kill it */
16532             if( count == -2 ) {
16533                 DestroyChildProcess( cps->pr, 9 );
16534                 cps->pr = NoProc;
16535             }
16536
16537             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16538         }
16539         return;
16540     }
16541
16542     if ((end_str = strchr(message, '\r')) != NULL)
16543       *end_str = NULLCHAR;
16544     if ((end_str = strchr(message, '\n')) != NULL)
16545       *end_str = NULLCHAR;
16546
16547     if (appData.debugMode) {
16548         TimeMark now; int print = 1;
16549         char *quote = ""; char c; int i;
16550
16551         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16552                 char start = message[0];
16553                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16554                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16555                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16556                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16557                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16558                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16559                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16560                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16561                    sscanf(message, "hint: %c", &c)!=1 &&
16562                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16563                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16564                     print = (appData.engineComments >= 2);
16565                 }
16566                 message[0] = start; // restore original message
16567         }
16568         if(print) {
16569                 GetTimeMark(&now);
16570                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16571                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16572                         quote,
16573                         message);
16574                 if(serverFP)
16575                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16576                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16577                         quote,
16578                         message), fflush(serverFP);
16579         }
16580     }
16581
16582     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16583     if (appData.icsEngineAnalyze) {
16584         if (strstr(message, "whisper") != NULL ||
16585              strstr(message, "kibitz") != NULL ||
16586             strstr(message, "tellics") != NULL) return;
16587     }
16588
16589     HandleMachineMove(message, cps);
16590 }
16591
16592
16593 void
16594 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16595 {
16596     char buf[MSG_SIZ];
16597     int seconds;
16598
16599     if( timeControl_2 > 0 ) {
16600         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16601             tc = timeControl_2;
16602         }
16603     }
16604     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16605     inc /= cps->timeOdds;
16606     st  /= cps->timeOdds;
16607
16608     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16609
16610     if (st > 0) {
16611       /* Set exact time per move, normally using st command */
16612       if (cps->stKludge) {
16613         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16614         seconds = st % 60;
16615         if (seconds == 0) {
16616           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16617         } else {
16618           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16619         }
16620       } else {
16621         snprintf(buf, MSG_SIZ, "st %d\n", st);
16622       }
16623     } else {
16624       /* Set conventional or incremental time control, using level command */
16625       if (seconds == 0) {
16626         /* Note old gnuchess bug -- minutes:seconds used to not work.
16627            Fixed in later versions, but still avoid :seconds
16628            when seconds is 0. */
16629         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16630       } else {
16631         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16632                  seconds, inc/1000.);
16633       }
16634     }
16635     SendToProgram(buf, cps);
16636
16637     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16638     /* Orthogonally, limit search to given depth */
16639     if (sd > 0) {
16640       if (cps->sdKludge) {
16641         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16642       } else {
16643         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16644       }
16645       SendToProgram(buf, cps);
16646     }
16647
16648     if(cps->nps >= 0) { /* [HGM] nps */
16649         if(cps->supportsNPS == FALSE)
16650           cps->nps = -1; // don't use if engine explicitly says not supported!
16651         else {
16652           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16653           SendToProgram(buf, cps);
16654         }
16655     }
16656 }
16657
16658 ChessProgramState *
16659 WhitePlayer ()
16660 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16661 {
16662     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16663        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16664         return &second;
16665     return &first;
16666 }
16667
16668 void
16669 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16670 {
16671     char message[MSG_SIZ];
16672     long time, otime;
16673
16674     /* Note: this routine must be called when the clocks are stopped
16675        or when they have *just* been set or switched; otherwise
16676        it will be off by the time since the current tick started.
16677     */
16678     if (machineWhite) {
16679         time = whiteTimeRemaining / 10;
16680         otime = blackTimeRemaining / 10;
16681     } else {
16682         time = blackTimeRemaining / 10;
16683         otime = whiteTimeRemaining / 10;
16684     }
16685     /* [HGM] translate opponent's time by time-odds factor */
16686     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16687
16688     if (time <= 0) time = 1;
16689     if (otime <= 0) otime = 1;
16690
16691     snprintf(message, MSG_SIZ, "time %ld\n", time);
16692     SendToProgram(message, cps);
16693
16694     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16695     SendToProgram(message, cps);
16696 }
16697
16698 char *
16699 EngineDefinedVariant (ChessProgramState *cps, int n)
16700 {   // return name of n-th unknown variant that engine supports
16701     static char buf[MSG_SIZ];
16702     char *p, *s = cps->variants;
16703     if(!s) return NULL;
16704     do { // parse string from variants feature
16705       VariantClass v;
16706         p = strchr(s, ',');
16707         if(p) *p = NULLCHAR;
16708       v = StringToVariant(s);
16709       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16710         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16711             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16712                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16713                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16714                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16715             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16716         }
16717         if(p) *p++ = ',';
16718         if(n < 0) return buf;
16719     } while(s = p);
16720     return NULL;
16721 }
16722
16723 int
16724 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16725 {
16726   char buf[MSG_SIZ];
16727   int len = strlen(name);
16728   int val;
16729
16730   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16731     (*p) += len + 1;
16732     sscanf(*p, "%d", &val);
16733     *loc = (val != 0);
16734     while (**p && **p != ' ')
16735       (*p)++;
16736     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16737     SendToProgram(buf, cps);
16738     return TRUE;
16739   }
16740   return FALSE;
16741 }
16742
16743 int
16744 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16745 {
16746   char buf[MSG_SIZ];
16747   int len = strlen(name);
16748   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16749     (*p) += len + 1;
16750     sscanf(*p, "%d", loc);
16751     while (**p && **p != ' ') (*p)++;
16752     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16753     SendToProgram(buf, cps);
16754     return TRUE;
16755   }
16756   return FALSE;
16757 }
16758
16759 int
16760 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16761 {
16762   char buf[MSG_SIZ];
16763   int len = strlen(name);
16764   if (strncmp((*p), name, len) == 0
16765       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16766     (*p) += len + 2;
16767     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16768     sscanf(*p, "%[^\"]", *loc);
16769     while (**p && **p != '\"') (*p)++;
16770     if (**p == '\"') (*p)++;
16771     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16772     SendToProgram(buf, cps);
16773     return TRUE;
16774   }
16775   return FALSE;
16776 }
16777
16778 int
16779 ParseOption (Option *opt, ChessProgramState *cps)
16780 // [HGM] options: process the string that defines an engine option, and determine
16781 // name, type, default value, and allowed value range
16782 {
16783         char *p, *q, buf[MSG_SIZ];
16784         int n, min = (-1)<<31, max = 1<<31, def;
16785
16786         if(p = strstr(opt->name, " -spin ")) {
16787             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16788             if(max < min) max = min; // enforce consistency
16789             if(def < min) def = min;
16790             if(def > max) def = max;
16791             opt->value = def;
16792             opt->min = min;
16793             opt->max = max;
16794             opt->type = Spin;
16795         } else if((p = strstr(opt->name, " -slider "))) {
16796             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16797             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16798             if(max < min) max = min; // enforce consistency
16799             if(def < min) def = min;
16800             if(def > max) def = max;
16801             opt->value = def;
16802             opt->min = min;
16803             opt->max = max;
16804             opt->type = Spin; // Slider;
16805         } else if((p = strstr(opt->name, " -string "))) {
16806             opt->textValue = p+9;
16807             opt->type = TextBox;
16808         } else if((p = strstr(opt->name, " -file "))) {
16809             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16810             opt->textValue = p+7;
16811             opt->type = FileName; // FileName;
16812         } else if((p = strstr(opt->name, " -path "))) {
16813             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16814             opt->textValue = p+7;
16815             opt->type = PathName; // PathName;
16816         } else if(p = strstr(opt->name, " -check ")) {
16817             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16818             opt->value = (def != 0);
16819             opt->type = CheckBox;
16820         } else if(p = strstr(opt->name, " -combo ")) {
16821             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16822             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16823             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16824             opt->value = n = 0;
16825             while(q = StrStr(q, " /// ")) {
16826                 n++; *q = 0;    // count choices, and null-terminate each of them
16827                 q += 5;
16828                 if(*q == '*') { // remember default, which is marked with * prefix
16829                     q++;
16830                     opt->value = n;
16831                 }
16832                 cps->comboList[cps->comboCnt++] = q;
16833             }
16834             cps->comboList[cps->comboCnt++] = NULL;
16835             opt->max = n + 1;
16836             opt->type = ComboBox;
16837         } else if(p = strstr(opt->name, " -button")) {
16838             opt->type = Button;
16839         } else if(p = strstr(opt->name, " -save")) {
16840             opt->type = SaveButton;
16841         } else return FALSE;
16842         *p = 0; // terminate option name
16843         // now look if the command-line options define a setting for this engine option.
16844         if(cps->optionSettings && cps->optionSettings[0])
16845             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16846         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16847           snprintf(buf, MSG_SIZ, "option %s", p);
16848                 if(p = strstr(buf, ",")) *p = 0;
16849                 if(q = strchr(buf, '=')) switch(opt->type) {
16850                     case ComboBox:
16851                         for(n=0; n<opt->max; n++)
16852                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16853                         break;
16854                     case TextBox:
16855                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16856                         break;
16857                     case Spin:
16858                     case CheckBox:
16859                         opt->value = atoi(q+1);
16860                     default:
16861                         break;
16862                 }
16863                 strcat(buf, "\n");
16864                 SendToProgram(buf, cps);
16865         }
16866         return TRUE;
16867 }
16868
16869 void
16870 FeatureDone (ChessProgramState *cps, int val)
16871 {
16872   DelayedEventCallback cb = GetDelayedEvent();
16873   if ((cb == InitBackEnd3 && cps == &first) ||
16874       (cb == SettingsMenuIfReady && cps == &second) ||
16875       (cb == LoadEngine) ||
16876       (cb == TwoMachinesEventIfReady)) {
16877     CancelDelayedEvent();
16878     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16879   }
16880   cps->initDone = val;
16881   if(val) cps->reload = FALSE;
16882 }
16883
16884 /* Parse feature command from engine */
16885 void
16886 ParseFeatures (char *args, ChessProgramState *cps)
16887 {
16888   char *p = args;
16889   char *q = NULL;
16890   int val;
16891   char buf[MSG_SIZ];
16892
16893   for (;;) {
16894     while (*p == ' ') p++;
16895     if (*p == NULLCHAR) return;
16896
16897     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16898     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16899     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16900     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16901     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16902     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16903     if (BoolFeature(&p, "reuse", &val, cps)) {
16904       /* Engine can disable reuse, but can't enable it if user said no */
16905       if (!val) cps->reuse = FALSE;
16906       continue;
16907     }
16908     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16909     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16910       if (gameMode == TwoMachinesPlay) {
16911         DisplayTwoMachinesTitle();
16912       } else {
16913         DisplayTitle("");
16914       }
16915       continue;
16916     }
16917     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16918     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16919     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16920     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16921     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16922     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16923     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16924     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16925     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16926     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16927     if (IntFeature(&p, "done", &val, cps)) {
16928       FeatureDone(cps, val);
16929       continue;
16930     }
16931     /* Added by Tord: */
16932     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16933     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16934     /* End of additions by Tord */
16935
16936     /* [HGM] added features: */
16937     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16938     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16939     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16940     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16941     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16942     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16943     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16944     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16945         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16946         FREE(cps->option[cps->nrOptions].name);
16947         cps->option[cps->nrOptions].name = q; q = NULL;
16948         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16949           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16950             SendToProgram(buf, cps);
16951             continue;
16952         }
16953         if(cps->nrOptions >= MAX_OPTIONS) {
16954             cps->nrOptions--;
16955             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16956             DisplayError(buf, 0);
16957         }
16958         continue;
16959     }
16960     /* End of additions by HGM */
16961
16962     /* unknown feature: complain and skip */
16963     q = p;
16964     while (*q && *q != '=') q++;
16965     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16966     SendToProgram(buf, cps);
16967     p = q;
16968     if (*p == '=') {
16969       p++;
16970       if (*p == '\"') {
16971         p++;
16972         while (*p && *p != '\"') p++;
16973         if (*p == '\"') p++;
16974       } else {
16975         while (*p && *p != ' ') p++;
16976       }
16977     }
16978   }
16979
16980 }
16981
16982 void
16983 PeriodicUpdatesEvent (int newState)
16984 {
16985     if (newState == appData.periodicUpdates)
16986       return;
16987
16988     appData.periodicUpdates=newState;
16989
16990     /* Display type changes, so update it now */
16991 //    DisplayAnalysis();
16992
16993     /* Get the ball rolling again... */
16994     if (newState) {
16995         AnalysisPeriodicEvent(1);
16996         StartAnalysisClock();
16997     }
16998 }
16999
17000 void
17001 PonderNextMoveEvent (int newState)
17002 {
17003     if (newState == appData.ponderNextMove) return;
17004     if (gameMode == EditPosition) EditPositionDone(TRUE);
17005     if (newState) {
17006         SendToProgram("hard\n", &first);
17007         if (gameMode == TwoMachinesPlay) {
17008             SendToProgram("hard\n", &second);
17009         }
17010     } else {
17011         SendToProgram("easy\n", &first);
17012         thinkOutput[0] = NULLCHAR;
17013         if (gameMode == TwoMachinesPlay) {
17014             SendToProgram("easy\n", &second);
17015         }
17016     }
17017     appData.ponderNextMove = newState;
17018 }
17019
17020 void
17021 NewSettingEvent (int option, int *feature, char *command, int value)
17022 {
17023     char buf[MSG_SIZ];
17024
17025     if (gameMode == EditPosition) EditPositionDone(TRUE);
17026     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17027     if(feature == NULL || *feature) SendToProgram(buf, &first);
17028     if (gameMode == TwoMachinesPlay) {
17029         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17030     }
17031 }
17032
17033 void
17034 ShowThinkingEvent ()
17035 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17036 {
17037     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17038     int newState = appData.showThinking
17039         // [HGM] thinking: other features now need thinking output as well
17040         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17041
17042     if (oldState == newState) return;
17043     oldState = newState;
17044     if (gameMode == EditPosition) EditPositionDone(TRUE);
17045     if (oldState) {
17046         SendToProgram("post\n", &first);
17047         if (gameMode == TwoMachinesPlay) {
17048             SendToProgram("post\n", &second);
17049         }
17050     } else {
17051         SendToProgram("nopost\n", &first);
17052         thinkOutput[0] = NULLCHAR;
17053         if (gameMode == TwoMachinesPlay) {
17054             SendToProgram("nopost\n", &second);
17055         }
17056     }
17057 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17058 }
17059
17060 void
17061 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17062 {
17063   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17064   if (pr == NoProc) return;
17065   AskQuestion(title, question, replyPrefix, pr);
17066 }
17067
17068 void
17069 TypeInEvent (char firstChar)
17070 {
17071     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17072         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17073         gameMode == AnalyzeMode || gameMode == EditGame ||
17074         gameMode == EditPosition || gameMode == IcsExamining ||
17075         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17076         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17077                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17078                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17079         gameMode == Training) PopUpMoveDialog(firstChar);
17080 }
17081
17082 void
17083 TypeInDoneEvent (char *move)
17084 {
17085         Board board;
17086         int n, fromX, fromY, toX, toY;
17087         char promoChar;
17088         ChessMove moveType;
17089
17090         // [HGM] FENedit
17091         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17092                 EditPositionPasteFEN(move);
17093                 return;
17094         }
17095         // [HGM] movenum: allow move number to be typed in any mode
17096         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17097           ToNrEvent(2*n-1);
17098           return;
17099         }
17100         // undocumented kludge: allow command-line option to be typed in!
17101         // (potentially fatal, and does not implement the effect of the option.)
17102         // should only be used for options that are values on which future decisions will be made,
17103         // and definitely not on options that would be used during initialization.
17104         if(strstr(move, "!!! -") == move) {
17105             ParseArgsFromString(move+4);
17106             return;
17107         }
17108
17109       if (gameMode != EditGame && currentMove != forwardMostMove &&
17110         gameMode != Training) {
17111         DisplayMoveError(_("Displayed move is not current"));
17112       } else {
17113         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17114           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17115         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17116         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17117           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17118           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17119         } else {
17120           DisplayMoveError(_("Could not parse move"));
17121         }
17122       }
17123 }
17124
17125 void
17126 DisplayMove (int moveNumber)
17127 {
17128     char message[MSG_SIZ];
17129     char res[MSG_SIZ];
17130     char cpThinkOutput[MSG_SIZ];
17131
17132     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17133
17134     if (moveNumber == forwardMostMove - 1 ||
17135         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17136
17137         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17138
17139         if (strchr(cpThinkOutput, '\n')) {
17140             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17141         }
17142     } else {
17143         *cpThinkOutput = NULLCHAR;
17144     }
17145
17146     /* [AS] Hide thinking from human user */
17147     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17148         *cpThinkOutput = NULLCHAR;
17149         if( thinkOutput[0] != NULLCHAR ) {
17150             int i;
17151
17152             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17153                 cpThinkOutput[i] = '.';
17154             }
17155             cpThinkOutput[i] = NULLCHAR;
17156             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17157         }
17158     }
17159
17160     if (moveNumber == forwardMostMove - 1 &&
17161         gameInfo.resultDetails != NULL) {
17162         if (gameInfo.resultDetails[0] == NULLCHAR) {
17163           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17164         } else {
17165           snprintf(res, MSG_SIZ, " {%s} %s",
17166                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17167         }
17168     } else {
17169         res[0] = NULLCHAR;
17170     }
17171
17172     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17173         DisplayMessage(res, cpThinkOutput);
17174     } else {
17175       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17176                 WhiteOnMove(moveNumber) ? " " : ".. ",
17177                 parseList[moveNumber], res);
17178         DisplayMessage(message, cpThinkOutput);
17179     }
17180 }
17181
17182 void
17183 DisplayComment (int moveNumber, char *text)
17184 {
17185     char title[MSG_SIZ];
17186
17187     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17188       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17189     } else {
17190       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17191               WhiteOnMove(moveNumber) ? " " : ".. ",
17192               parseList[moveNumber]);
17193     }
17194     if (text != NULL && (appData.autoDisplayComment || commentUp))
17195         CommentPopUp(title, text);
17196 }
17197
17198 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17199  * might be busy thinking or pondering.  It can be omitted if your
17200  * gnuchess is configured to stop thinking immediately on any user
17201  * input.  However, that gnuchess feature depends on the FIONREAD
17202  * ioctl, which does not work properly on some flavors of Unix.
17203  */
17204 void
17205 Attention (ChessProgramState *cps)
17206 {
17207 #if ATTENTION
17208     if (!cps->useSigint) return;
17209     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17210     switch (gameMode) {
17211       case MachinePlaysWhite:
17212       case MachinePlaysBlack:
17213       case TwoMachinesPlay:
17214       case IcsPlayingWhite:
17215       case IcsPlayingBlack:
17216       case AnalyzeMode:
17217       case AnalyzeFile:
17218         /* Skip if we know it isn't thinking */
17219         if (!cps->maybeThinking) return;
17220         if (appData.debugMode)
17221           fprintf(debugFP, "Interrupting %s\n", cps->which);
17222         InterruptChildProcess(cps->pr);
17223         cps->maybeThinking = FALSE;
17224         break;
17225       default:
17226         break;
17227     }
17228 #endif /*ATTENTION*/
17229 }
17230
17231 int
17232 CheckFlags ()
17233 {
17234     if (whiteTimeRemaining <= 0) {
17235         if (!whiteFlag) {
17236             whiteFlag = TRUE;
17237             if (appData.icsActive) {
17238                 if (appData.autoCallFlag &&
17239                     gameMode == IcsPlayingBlack && !blackFlag) {
17240                   SendToICS(ics_prefix);
17241                   SendToICS("flag\n");
17242                 }
17243             } else {
17244                 if (blackFlag) {
17245                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17246                 } else {
17247                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17248                     if (appData.autoCallFlag) {
17249                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17250                         return TRUE;
17251                     }
17252                 }
17253             }
17254         }
17255     }
17256     if (blackTimeRemaining <= 0) {
17257         if (!blackFlag) {
17258             blackFlag = TRUE;
17259             if (appData.icsActive) {
17260                 if (appData.autoCallFlag &&
17261                     gameMode == IcsPlayingWhite && !whiteFlag) {
17262                   SendToICS(ics_prefix);
17263                   SendToICS("flag\n");
17264                 }
17265             } else {
17266                 if (whiteFlag) {
17267                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17268                 } else {
17269                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17270                     if (appData.autoCallFlag) {
17271                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17272                         return TRUE;
17273                     }
17274                 }
17275             }
17276         }
17277     }
17278     return FALSE;
17279 }
17280
17281 void
17282 CheckTimeControl ()
17283 {
17284     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17285         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17286
17287     /*
17288      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17289      */
17290     if ( !WhiteOnMove(forwardMostMove) ) {
17291         /* White made time control */
17292         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17293         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17294         /* [HGM] time odds: correct new time quota for time odds! */
17295                                             / WhitePlayer()->timeOdds;
17296         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17297     } else {
17298         lastBlack -= blackTimeRemaining;
17299         /* Black made time control */
17300         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17301                                             / WhitePlayer()->other->timeOdds;
17302         lastWhite = whiteTimeRemaining;
17303     }
17304 }
17305
17306 void
17307 DisplayBothClocks ()
17308 {
17309     int wom = gameMode == EditPosition ?
17310       !blackPlaysFirst : WhiteOnMove(currentMove);
17311     DisplayWhiteClock(whiteTimeRemaining, wom);
17312     DisplayBlackClock(blackTimeRemaining, !wom);
17313 }
17314
17315
17316 /* Timekeeping seems to be a portability nightmare.  I think everyone
17317    has ftime(), but I'm really not sure, so I'm including some ifdefs
17318    to use other calls if you don't.  Clocks will be less accurate if
17319    you have neither ftime nor gettimeofday.
17320 */
17321
17322 /* VS 2008 requires the #include outside of the function */
17323 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17324 #include <sys/timeb.h>
17325 #endif
17326
17327 /* Get the current time as a TimeMark */
17328 void
17329 GetTimeMark (TimeMark *tm)
17330 {
17331 #if HAVE_GETTIMEOFDAY
17332
17333     struct timeval timeVal;
17334     struct timezone timeZone;
17335
17336     gettimeofday(&timeVal, &timeZone);
17337     tm->sec = (long) timeVal.tv_sec;
17338     tm->ms = (int) (timeVal.tv_usec / 1000L);
17339
17340 #else /*!HAVE_GETTIMEOFDAY*/
17341 #if HAVE_FTIME
17342
17343 // include <sys/timeb.h> / moved to just above start of function
17344     struct timeb timeB;
17345
17346     ftime(&timeB);
17347     tm->sec = (long) timeB.time;
17348     tm->ms = (int) timeB.millitm;
17349
17350 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17351     tm->sec = (long) time(NULL);
17352     tm->ms = 0;
17353 #endif
17354 #endif
17355 }
17356
17357 /* Return the difference in milliseconds between two
17358    time marks.  We assume the difference will fit in a long!
17359 */
17360 long
17361 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17362 {
17363     return 1000L*(tm2->sec - tm1->sec) +
17364            (long) (tm2->ms - tm1->ms);
17365 }
17366
17367
17368 /*
17369  * Code to manage the game clocks.
17370  *
17371  * In tournament play, black starts the clock and then white makes a move.
17372  * We give the human user a slight advantage if he is playing white---the
17373  * clocks don't run until he makes his first move, so it takes zero time.
17374  * Also, we don't account for network lag, so we could get out of sync
17375  * with GNU Chess's clock -- but then, referees are always right.
17376  */
17377
17378 static TimeMark tickStartTM;
17379 static long intendedTickLength;
17380
17381 long
17382 NextTickLength (long timeRemaining)
17383 {
17384     long nominalTickLength, nextTickLength;
17385
17386     if (timeRemaining > 0L && timeRemaining <= 10000L)
17387       nominalTickLength = 100L;
17388     else
17389       nominalTickLength = 1000L;
17390     nextTickLength = timeRemaining % nominalTickLength;
17391     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17392
17393     return nextTickLength;
17394 }
17395
17396 /* Adjust clock one minute up or down */
17397 void
17398 AdjustClock (Boolean which, int dir)
17399 {
17400     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17401     if(which) blackTimeRemaining += 60000*dir;
17402     else      whiteTimeRemaining += 60000*dir;
17403     DisplayBothClocks();
17404     adjustedClock = TRUE;
17405 }
17406
17407 /* Stop clocks and reset to a fresh time control */
17408 void
17409 ResetClocks ()
17410 {
17411     (void) StopClockTimer();
17412     if (appData.icsActive) {
17413         whiteTimeRemaining = blackTimeRemaining = 0;
17414     } else if (searchTime) {
17415         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17416         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17417     } else { /* [HGM] correct new time quote for time odds */
17418         whiteTC = blackTC = fullTimeControlString;
17419         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17420         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17421     }
17422     if (whiteFlag || blackFlag) {
17423         DisplayTitle("");
17424         whiteFlag = blackFlag = FALSE;
17425     }
17426     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17427     DisplayBothClocks();
17428     adjustedClock = FALSE;
17429 }
17430
17431 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17432
17433 /* Decrement running clock by amount of time that has passed */
17434 void
17435 DecrementClocks ()
17436 {
17437     long timeRemaining;
17438     long lastTickLength, fudge;
17439     TimeMark now;
17440
17441     if (!appData.clockMode) return;
17442     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17443
17444     GetTimeMark(&now);
17445
17446     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17447
17448     /* Fudge if we woke up a little too soon */
17449     fudge = intendedTickLength - lastTickLength;
17450     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17451
17452     if (WhiteOnMove(forwardMostMove)) {
17453         if(whiteNPS >= 0) lastTickLength = 0;
17454         timeRemaining = whiteTimeRemaining -= lastTickLength;
17455         if(timeRemaining < 0 && !appData.icsActive) {
17456             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17457             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17458                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17459                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17460             }
17461         }
17462         DisplayWhiteClock(whiteTimeRemaining - fudge,
17463                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17464     } else {
17465         if(blackNPS >= 0) lastTickLength = 0;
17466         timeRemaining = blackTimeRemaining -= lastTickLength;
17467         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17468             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17469             if(suddenDeath) {
17470                 blackStartMove = forwardMostMove;
17471                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17472             }
17473         }
17474         DisplayBlackClock(blackTimeRemaining - fudge,
17475                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17476     }
17477     if (CheckFlags()) return;
17478
17479     if(twoBoards) { // count down secondary board's clocks as well
17480         activePartnerTime -= lastTickLength;
17481         partnerUp = 1;
17482         if(activePartner == 'W')
17483             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17484         else
17485             DisplayBlackClock(activePartnerTime, TRUE);
17486         partnerUp = 0;
17487     }
17488
17489     tickStartTM = now;
17490     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17491     StartClockTimer(intendedTickLength);
17492
17493     /* if the time remaining has fallen below the alarm threshold, sound the
17494      * alarm. if the alarm has sounded and (due to a takeback or time control
17495      * with increment) the time remaining has increased to a level above the
17496      * threshold, reset the alarm so it can sound again.
17497      */
17498
17499     if (appData.icsActive && appData.icsAlarm) {
17500
17501         /* make sure we are dealing with the user's clock */
17502         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17503                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17504            )) return;
17505
17506         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17507             alarmSounded = FALSE;
17508         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17509             PlayAlarmSound();
17510             alarmSounded = TRUE;
17511         }
17512     }
17513 }
17514
17515
17516 /* A player has just moved, so stop the previously running
17517    clock and (if in clock mode) start the other one.
17518    We redisplay both clocks in case we're in ICS mode, because
17519    ICS gives us an update to both clocks after every move.
17520    Note that this routine is called *after* forwardMostMove
17521    is updated, so the last fractional tick must be subtracted
17522    from the color that is *not* on move now.
17523 */
17524 void
17525 SwitchClocks (int newMoveNr)
17526 {
17527     long lastTickLength;
17528     TimeMark now;
17529     int flagged = FALSE;
17530
17531     GetTimeMark(&now);
17532
17533     if (StopClockTimer() && appData.clockMode) {
17534         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17535         if (!WhiteOnMove(forwardMostMove)) {
17536             if(blackNPS >= 0) lastTickLength = 0;
17537             blackTimeRemaining -= lastTickLength;
17538            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17539 //         if(pvInfoList[forwardMostMove].time == -1)
17540                  pvInfoList[forwardMostMove].time =               // use GUI time
17541                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17542         } else {
17543            if(whiteNPS >= 0) lastTickLength = 0;
17544            whiteTimeRemaining -= lastTickLength;
17545            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17546 //         if(pvInfoList[forwardMostMove].time == -1)
17547                  pvInfoList[forwardMostMove].time =
17548                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17549         }
17550         flagged = CheckFlags();
17551     }
17552     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17553     CheckTimeControl();
17554
17555     if (flagged || !appData.clockMode) return;
17556
17557     switch (gameMode) {
17558       case MachinePlaysBlack:
17559       case MachinePlaysWhite:
17560       case BeginningOfGame:
17561         if (pausing) return;
17562         break;
17563
17564       case EditGame:
17565       case PlayFromGameFile:
17566       case IcsExamining:
17567         return;
17568
17569       default:
17570         break;
17571     }
17572
17573     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17574         if(WhiteOnMove(forwardMostMove))
17575              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17576         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17577     }
17578
17579     tickStartTM = now;
17580     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17581       whiteTimeRemaining : blackTimeRemaining);
17582     StartClockTimer(intendedTickLength);
17583 }
17584
17585
17586 /* Stop both clocks */
17587 void
17588 StopClocks ()
17589 {
17590     long lastTickLength;
17591     TimeMark now;
17592
17593     if (!StopClockTimer()) return;
17594     if (!appData.clockMode) return;
17595
17596     GetTimeMark(&now);
17597
17598     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17599     if (WhiteOnMove(forwardMostMove)) {
17600         if(whiteNPS >= 0) lastTickLength = 0;
17601         whiteTimeRemaining -= lastTickLength;
17602         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17603     } else {
17604         if(blackNPS >= 0) lastTickLength = 0;
17605         blackTimeRemaining -= lastTickLength;
17606         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17607     }
17608     CheckFlags();
17609 }
17610
17611 /* Start clock of player on move.  Time may have been reset, so
17612    if clock is already running, stop and restart it. */
17613 void
17614 StartClocks ()
17615 {
17616     (void) StopClockTimer(); /* in case it was running already */
17617     DisplayBothClocks();
17618     if (CheckFlags()) return;
17619
17620     if (!appData.clockMode) return;
17621     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17622
17623     GetTimeMark(&tickStartTM);
17624     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17625       whiteTimeRemaining : blackTimeRemaining);
17626
17627    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17628     whiteNPS = blackNPS = -1;
17629     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17630        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17631         whiteNPS = first.nps;
17632     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17633        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17634         blackNPS = first.nps;
17635     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17636         whiteNPS = second.nps;
17637     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17638         blackNPS = second.nps;
17639     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17640
17641     StartClockTimer(intendedTickLength);
17642 }
17643
17644 char *
17645 TimeString (long ms)
17646 {
17647     long second, minute, hour, day;
17648     char *sign = "";
17649     static char buf[32];
17650
17651     if (ms > 0 && ms <= 9900) {
17652       /* convert milliseconds to tenths, rounding up */
17653       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17654
17655       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17656       return buf;
17657     }
17658
17659     /* convert milliseconds to seconds, rounding up */
17660     /* use floating point to avoid strangeness of integer division
17661        with negative dividends on many machines */
17662     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17663
17664     if (second < 0) {
17665         sign = "-";
17666         second = -second;
17667     }
17668
17669     day = second / (60 * 60 * 24);
17670     second = second % (60 * 60 * 24);
17671     hour = second / (60 * 60);
17672     second = second % (60 * 60);
17673     minute = second / 60;
17674     second = second % 60;
17675
17676     if (day > 0)
17677       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17678               sign, day, hour, minute, second);
17679     else if (hour > 0)
17680       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17681     else
17682       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17683
17684     return buf;
17685 }
17686
17687
17688 /*
17689  * This is necessary because some C libraries aren't ANSI C compliant yet.
17690  */
17691 char *
17692 StrStr (char *string, char *match)
17693 {
17694     int i, length;
17695
17696     length = strlen(match);
17697
17698     for (i = strlen(string) - length; i >= 0; i--, string++)
17699       if (!strncmp(match, string, length))
17700         return string;
17701
17702     return NULL;
17703 }
17704
17705 char *
17706 StrCaseStr (char *string, char *match)
17707 {
17708     int i, j, length;
17709
17710     length = strlen(match);
17711
17712     for (i = strlen(string) - length; i >= 0; i--, string++) {
17713         for (j = 0; j < length; j++) {
17714             if (ToLower(match[j]) != ToLower(string[j]))
17715               break;
17716         }
17717         if (j == length) return string;
17718     }
17719
17720     return NULL;
17721 }
17722
17723 #ifndef _amigados
17724 int
17725 StrCaseCmp (char *s1, char *s2)
17726 {
17727     char c1, c2;
17728
17729     for (;;) {
17730         c1 = ToLower(*s1++);
17731         c2 = ToLower(*s2++);
17732         if (c1 > c2) return 1;
17733         if (c1 < c2) return -1;
17734         if (c1 == NULLCHAR) return 0;
17735     }
17736 }
17737
17738
17739 int
17740 ToLower (int c)
17741 {
17742     return isupper(c) ? tolower(c) : c;
17743 }
17744
17745
17746 int
17747 ToUpper (int c)
17748 {
17749     return islower(c) ? toupper(c) : c;
17750 }
17751 #endif /* !_amigados    */
17752
17753 char *
17754 StrSave (char *s)
17755 {
17756   char *ret;
17757
17758   if ((ret = (char *) malloc(strlen(s) + 1)))
17759     {
17760       safeStrCpy(ret, s, strlen(s)+1);
17761     }
17762   return ret;
17763 }
17764
17765 char *
17766 StrSavePtr (char *s, char **savePtr)
17767 {
17768     if (*savePtr) {
17769         free(*savePtr);
17770     }
17771     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17772       safeStrCpy(*savePtr, s, strlen(s)+1);
17773     }
17774     return(*savePtr);
17775 }
17776
17777 char *
17778 PGNDate ()
17779 {
17780     time_t clock;
17781     struct tm *tm;
17782     char buf[MSG_SIZ];
17783
17784     clock = time((time_t *)NULL);
17785     tm = localtime(&clock);
17786     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17787             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17788     return StrSave(buf);
17789 }
17790
17791
17792 char *
17793 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17794 {
17795     int i, j, fromX, fromY, toX, toY;
17796     int whiteToPlay;
17797     char buf[MSG_SIZ];
17798     char *p, *q;
17799     int emptycount;
17800     ChessSquare piece;
17801
17802     whiteToPlay = (gameMode == EditPosition) ?
17803       !blackPlaysFirst : (move % 2 == 0);
17804     p = buf;
17805
17806     /* Piece placement data */
17807     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17808         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17809         emptycount = 0;
17810         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17811             if (boards[move][i][j] == EmptySquare) {
17812                 emptycount++;
17813             } else { ChessSquare piece = boards[move][i][j];
17814                 if (emptycount > 0) {
17815                     if(emptycount<10) /* [HGM] can be >= 10 */
17816                         *p++ = '0' + emptycount;
17817                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17818                     emptycount = 0;
17819                 }
17820                 if(PieceToChar(piece) == '+') {
17821                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17822                     *p++ = '+';
17823                     piece = (ChessSquare)(CHUDEMOTED piece);
17824                 }
17825                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17826                 if(p[-1] == '~') {
17827                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17828                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17829                     *p++ = '~';
17830                 }
17831             }
17832         }
17833         if (emptycount > 0) {
17834             if(emptycount<10) /* [HGM] can be >= 10 */
17835                 *p++ = '0' + emptycount;
17836             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17837             emptycount = 0;
17838         }
17839         *p++ = '/';
17840     }
17841     *(p - 1) = ' ';
17842
17843     /* [HGM] print Crazyhouse or Shogi holdings */
17844     if( gameInfo.holdingsWidth ) {
17845         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17846         q = p;
17847         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17848             piece = boards[move][i][BOARD_WIDTH-1];
17849             if( piece != EmptySquare )
17850               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17851                   *p++ = PieceToChar(piece);
17852         }
17853         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17854             piece = boards[move][BOARD_HEIGHT-i-1][0];
17855             if( piece != EmptySquare )
17856               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17857                   *p++ = PieceToChar(piece);
17858         }
17859
17860         if( q == p ) *p++ = '-';
17861         *p++ = ']';
17862         *p++ = ' ';
17863     }
17864
17865     /* Active color */
17866     *p++ = whiteToPlay ? 'w' : 'b';
17867     *p++ = ' ';
17868
17869   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17870     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17871   } else {
17872   if(nrCastlingRights) {
17873      int handW=0, handB=0;
17874      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17875         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17876         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17877      }
17878      q = p;
17879      if(appData.fischerCastling) {
17880         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17881            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17882                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17883         } else {
17884        /* [HGM] write directly from rights */
17885            if(boards[move][CASTLING][2] != NoRights &&
17886               boards[move][CASTLING][0] != NoRights   )
17887                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17888            if(boards[move][CASTLING][2] != NoRights &&
17889               boards[move][CASTLING][1] != NoRights   )
17890                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17891         }
17892         if(handB) {
17893            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17894                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17895         } else {
17896            if(boards[move][CASTLING][5] != NoRights &&
17897               boards[move][CASTLING][3] != NoRights   )
17898                 *p++ = boards[move][CASTLING][3] + AAA;
17899            if(boards[move][CASTLING][5] != NoRights &&
17900               boards[move][CASTLING][4] != NoRights   )
17901                 *p++ = boards[move][CASTLING][4] + AAA;
17902         }
17903      } else {
17904
17905         /* [HGM] write true castling rights */
17906         if( nrCastlingRights == 6 ) {
17907             int q, k=0;
17908             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17909                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17910             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17911                  boards[move][CASTLING][2] != NoRights  );
17912             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17913                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17914                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17915                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17916             }
17917             if(q) *p++ = 'Q';
17918             k = 0;
17919             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17920                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17921             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17922                  boards[move][CASTLING][5] != NoRights  );
17923             if(handB) {
17924                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17925                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17926                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17927             }
17928             if(q) *p++ = 'q';
17929         }
17930      }
17931      if (q == p) *p++ = '-'; /* No castling rights */
17932      *p++ = ' ';
17933   }
17934
17935   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17936      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17937      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17938     /* En passant target square */
17939     if (move > backwardMostMove) {
17940         fromX = moveList[move - 1][0] - AAA;
17941         fromY = moveList[move - 1][1] - ONE;
17942         toX = moveList[move - 1][2] - AAA;
17943         toY = moveList[move - 1][3] - ONE;
17944         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17945             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17946             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17947             fromX == toX) {
17948             /* 2-square pawn move just happened */
17949             *p++ = toX + AAA;
17950             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17951         } else {
17952             *p++ = '-';
17953         }
17954     } else if(move == backwardMostMove) {
17955         // [HGM] perhaps we should always do it like this, and forget the above?
17956         if((signed char)boards[move][EP_STATUS] >= 0) {
17957             *p++ = boards[move][EP_STATUS] + AAA;
17958             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17959         } else {
17960             *p++ = '-';
17961         }
17962     } else {
17963         *p++ = '-';
17964     }
17965     *p++ = ' ';
17966   }
17967   }
17968
17969     if(moveCounts)
17970     {   int i = 0, j=move;
17971
17972         /* [HGM] find reversible plies */
17973         if (appData.debugMode) { int k;
17974             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17975             for(k=backwardMostMove; k<=forwardMostMove; k++)
17976                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17977
17978         }
17979
17980         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17981         if( j == backwardMostMove ) i += initialRulePlies;
17982         sprintf(p, "%d ", i);
17983         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17984
17985         /* Fullmove number */
17986         sprintf(p, "%d", (move / 2) + 1);
17987     } else *--p = NULLCHAR;
17988
17989     return StrSave(buf);
17990 }
17991
17992 Boolean
17993 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17994 {
17995     int i, j, k, w=0, subst=0, shuffle=0;
17996     char *p, c;
17997     int emptycount, virgin[BOARD_FILES];
17998     ChessSquare piece;
17999
18000     p = fen;
18001
18002     /* Piece placement data */
18003     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18004         j = 0;
18005         for (;;) {
18006             if (*p == '/' || *p == ' ' || *p == '[' ) {
18007                 if(j > w) w = j;
18008                 emptycount = gameInfo.boardWidth - j;
18009                 while (emptycount--)
18010                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18011                 if (*p == '/') p++;
18012                 else if(autoSize) { // we stumbled unexpectedly into end of board
18013                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18014                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18015                     }
18016                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18017                 }
18018                 break;
18019 #if(BOARD_FILES >= 10)*0
18020             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18021                 p++; emptycount=10;
18022                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18023                 while (emptycount--)
18024                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18025 #endif
18026             } else if (*p == '*') {
18027                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18028             } else if (isdigit(*p)) {
18029                 emptycount = *p++ - '0';
18030                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18031                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18032                 while (emptycount--)
18033                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18034             } else if (*p == '<') {
18035                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18036                 else if (i != 0 || !shuffle) return FALSE;
18037                 p++;
18038             } else if (shuffle && *p == '>') {
18039                 p++; // for now ignore closing shuffle range, and assume rank-end
18040             } else if (*p == '?') {
18041                 if (j >= gameInfo.boardWidth) return FALSE;
18042                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18043                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18044             } else if (*p == '+' || isalpha(*p)) {
18045                 if (j >= gameInfo.boardWidth) return FALSE;
18046                 if(*p=='+') {
18047                     piece = CharToPiece(*++p);
18048                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18049                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18050                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18051                 } else piece = CharToPiece(*p++);
18052
18053                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18054                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18055                     piece = (ChessSquare) (PROMOTED piece);
18056                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18057                     p++;
18058                 }
18059                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18060             } else {
18061                 return FALSE;
18062             }
18063         }
18064     }
18065     while (*p == '/' || *p == ' ') p++;
18066
18067     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18068
18069     /* [HGM] by default clear Crazyhouse holdings, if present */
18070     if(gameInfo.holdingsWidth) {
18071        for(i=0; i<BOARD_HEIGHT; i++) {
18072            board[i][0]             = EmptySquare; /* black holdings */
18073            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18074            board[i][1]             = (ChessSquare) 0; /* black counts */
18075            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18076        }
18077     }
18078
18079     /* [HGM] look for Crazyhouse holdings here */
18080     while(*p==' ') p++;
18081     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18082         int swap=0, wcnt=0, bcnt=0;
18083         if(*p == '[') p++;
18084         if(*p == '<') swap++, p++;
18085         if(*p == '-' ) p++; /* empty holdings */ else {
18086             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18087             /* if we would allow FEN reading to set board size, we would   */
18088             /* have to add holdings and shift the board read so far here   */
18089             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18090                 p++;
18091                 if((int) piece >= (int) BlackPawn ) {
18092                     i = (int)piece - (int)BlackPawn;
18093                     i = PieceToNumber((ChessSquare)i);
18094                     if( i >= gameInfo.holdingsSize ) return FALSE;
18095                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18096                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18097                     bcnt++;
18098                 } else {
18099                     i = (int)piece - (int)WhitePawn;
18100                     i = PieceToNumber((ChessSquare)i);
18101                     if( i >= gameInfo.holdingsSize ) return FALSE;
18102                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18103                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18104                     wcnt++;
18105                 }
18106             }
18107             if(subst) { // substitute back-rank question marks by holdings pieces
18108                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18109                     int k, m, n = bcnt + 1;
18110                     if(board[0][j] == ClearBoard) {
18111                         if(!wcnt) return FALSE;
18112                         n = rand() % wcnt;
18113                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18114                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18115                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18116                             break;
18117                         }
18118                     }
18119                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18120                         if(!bcnt) return FALSE;
18121                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18122                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18123                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18124                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18125                             break;
18126                         }
18127                     }
18128                 }
18129                 subst = 0;
18130             }
18131         }
18132         if(*p == ']') p++;
18133     }
18134
18135     if(subst) return FALSE; // substitution requested, but no holdings
18136
18137     while(*p == ' ') p++;
18138
18139     /* Active color */
18140     c = *p++;
18141     if(appData.colorNickNames) {
18142       if( c == appData.colorNickNames[0] ) c = 'w'; else
18143       if( c == appData.colorNickNames[1] ) c = 'b';
18144     }
18145     switch (c) {
18146       case 'w':
18147         *blackPlaysFirst = FALSE;
18148         break;
18149       case 'b':
18150         *blackPlaysFirst = TRUE;
18151         break;
18152       default:
18153         return FALSE;
18154     }
18155
18156     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18157     /* return the extra info in global variiables             */
18158
18159     /* set defaults in case FEN is incomplete */
18160     board[EP_STATUS] = EP_UNKNOWN;
18161     for(i=0; i<nrCastlingRights; i++ ) {
18162         board[CASTLING][i] =
18163             appData.fischerCastling ? NoRights : initialRights[i];
18164     }   /* assume possible unless obviously impossible */
18165     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18166     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18167     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18168                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18169     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18170     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18171     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18172                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18173     FENrulePlies = 0;
18174
18175     while(*p==' ') p++;
18176     if(nrCastlingRights) {
18177       int fischer = 0;
18178       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18179       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18180           /* castling indicator present, so default becomes no castlings */
18181           for(i=0; i<nrCastlingRights; i++ ) {
18182                  board[CASTLING][i] = NoRights;
18183           }
18184       }
18185       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18186              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18187              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18188              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18189         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18190
18191         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18192             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18193             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18194         }
18195         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18196             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18197         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18198                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18199         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18200                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18201         switch(c) {
18202           case'K':
18203               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18204               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18205               board[CASTLING][2] = whiteKingFile;
18206               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18207               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18208               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18209               break;
18210           case'Q':
18211               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18212               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18213               board[CASTLING][2] = whiteKingFile;
18214               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18215               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18216               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18217               break;
18218           case'k':
18219               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18220               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18221               board[CASTLING][5] = blackKingFile;
18222               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18223               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18224               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18225               break;
18226           case'q':
18227               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18228               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18229               board[CASTLING][5] = blackKingFile;
18230               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18231               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18232               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18233           case '-':
18234               break;
18235           default: /* FRC castlings */
18236               if(c >= 'a') { /* black rights */
18237                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18238                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18239                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18240                   if(i == BOARD_RGHT) break;
18241                   board[CASTLING][5] = i;
18242                   c -= AAA;
18243                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18244                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18245                   if(c > i)
18246                       board[CASTLING][3] = c;
18247                   else
18248                       board[CASTLING][4] = c;
18249               } else { /* white rights */
18250                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18251                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18252                     if(board[0][i] == WhiteKing) break;
18253                   if(i == BOARD_RGHT) break;
18254                   board[CASTLING][2] = i;
18255                   c -= AAA - 'a' + 'A';
18256                   if(board[0][c] >= WhiteKing) break;
18257                   if(c > i)
18258                       board[CASTLING][0] = c;
18259                   else
18260                       board[CASTLING][1] = c;
18261               }
18262         }
18263       }
18264       for(i=0; i<nrCastlingRights; i++)
18265         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18266       if(gameInfo.variant == VariantSChess)
18267         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18268       if(fischer && shuffle) appData.fischerCastling = TRUE;
18269     if (appData.debugMode) {
18270         fprintf(debugFP, "FEN castling rights:");
18271         for(i=0; i<nrCastlingRights; i++)
18272         fprintf(debugFP, " %d", board[CASTLING][i]);
18273         fprintf(debugFP, "\n");
18274     }
18275
18276       while(*p==' ') p++;
18277     }
18278
18279     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18280
18281     /* read e.p. field in games that know e.p. capture */
18282     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18283        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18284        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18285       if(*p=='-') {
18286         p++; board[EP_STATUS] = EP_NONE;
18287       } else {
18288          char c = *p++ - AAA;
18289
18290          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18291          if(*p >= '0' && *p <='9') p++;
18292          board[EP_STATUS] = c;
18293       }
18294     }
18295
18296
18297     if(sscanf(p, "%d", &i) == 1) {
18298         FENrulePlies = i; /* 50-move ply counter */
18299         /* (The move number is still ignored)    */
18300     }
18301
18302     return TRUE;
18303 }
18304
18305 void
18306 EditPositionPasteFEN (char *fen)
18307 {
18308   if (fen != NULL) {
18309     Board initial_position;
18310
18311     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18312       DisplayError(_("Bad FEN position in clipboard"), 0);
18313       return ;
18314     } else {
18315       int savedBlackPlaysFirst = blackPlaysFirst;
18316       EditPositionEvent();
18317       blackPlaysFirst = savedBlackPlaysFirst;
18318       CopyBoard(boards[0], initial_position);
18319       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18320       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18321       DisplayBothClocks();
18322       DrawPosition(FALSE, boards[currentMove]);
18323     }
18324   }
18325 }
18326
18327 static char cseq[12] = "\\   ";
18328
18329 Boolean
18330 set_cont_sequence (char *new_seq)
18331 {
18332     int len;
18333     Boolean ret;
18334
18335     // handle bad attempts to set the sequence
18336         if (!new_seq)
18337                 return 0; // acceptable error - no debug
18338
18339     len = strlen(new_seq);
18340     ret = (len > 0) && (len < sizeof(cseq));
18341     if (ret)
18342       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18343     else if (appData.debugMode)
18344       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18345     return ret;
18346 }
18347
18348 /*
18349     reformat a source message so words don't cross the width boundary.  internal
18350     newlines are not removed.  returns the wrapped size (no null character unless
18351     included in source message).  If dest is NULL, only calculate the size required
18352     for the dest buffer.  lp argument indicats line position upon entry, and it's
18353     passed back upon exit.
18354 */
18355 int
18356 wrap (char *dest, char *src, int count, int width, int *lp)
18357 {
18358     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18359
18360     cseq_len = strlen(cseq);
18361     old_line = line = *lp;
18362     ansi = len = clen = 0;
18363
18364     for (i=0; i < count; i++)
18365     {
18366         if (src[i] == '\033')
18367             ansi = 1;
18368
18369         // if we hit the width, back up
18370         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18371         {
18372             // store i & len in case the word is too long
18373             old_i = i, old_len = len;
18374
18375             // find the end of the last word
18376             while (i && src[i] != ' ' && src[i] != '\n')
18377             {
18378                 i--;
18379                 len--;
18380             }
18381
18382             // word too long?  restore i & len before splitting it
18383             if ((old_i-i+clen) >= width)
18384             {
18385                 i = old_i;
18386                 len = old_len;
18387             }
18388
18389             // extra space?
18390             if (i && src[i-1] == ' ')
18391                 len--;
18392
18393             if (src[i] != ' ' && src[i] != '\n')
18394             {
18395                 i--;
18396                 if (len)
18397                     len--;
18398             }
18399
18400             // now append the newline and continuation sequence
18401             if (dest)
18402                 dest[len] = '\n';
18403             len++;
18404             if (dest)
18405                 strncpy(dest+len, cseq, cseq_len);
18406             len += cseq_len;
18407             line = cseq_len;
18408             clen = cseq_len;
18409             continue;
18410         }
18411
18412         if (dest)
18413             dest[len] = src[i];
18414         len++;
18415         if (!ansi)
18416             line++;
18417         if (src[i] == '\n')
18418             line = 0;
18419         if (src[i] == 'm')
18420             ansi = 0;
18421     }
18422     if (dest && appData.debugMode)
18423     {
18424         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18425             count, width, line, len, *lp);
18426         show_bytes(debugFP, src, count);
18427         fprintf(debugFP, "\ndest: ");
18428         show_bytes(debugFP, dest, len);
18429         fprintf(debugFP, "\n");
18430     }
18431     *lp = dest ? line : old_line;
18432
18433     return len;
18434 }
18435
18436 // [HGM] vari: routines for shelving variations
18437 Boolean modeRestore = FALSE;
18438
18439 void
18440 PushInner (int firstMove, int lastMove)
18441 {
18442         int i, j, nrMoves = lastMove - firstMove;
18443
18444         // push current tail of game on stack
18445         savedResult[storedGames] = gameInfo.result;
18446         savedDetails[storedGames] = gameInfo.resultDetails;
18447         gameInfo.resultDetails = NULL;
18448         savedFirst[storedGames] = firstMove;
18449         savedLast [storedGames] = lastMove;
18450         savedFramePtr[storedGames] = framePtr;
18451         framePtr -= nrMoves; // reserve space for the boards
18452         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18453             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18454             for(j=0; j<MOVE_LEN; j++)
18455                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18456             for(j=0; j<2*MOVE_LEN; j++)
18457                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18458             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18459             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18460             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18461             pvInfoList[firstMove+i-1].depth = 0;
18462             commentList[framePtr+i] = commentList[firstMove+i];
18463             commentList[firstMove+i] = NULL;
18464         }
18465
18466         storedGames++;
18467         forwardMostMove = firstMove; // truncate game so we can start variation
18468 }
18469
18470 void
18471 PushTail (int firstMove, int lastMove)
18472 {
18473         if(appData.icsActive) { // only in local mode
18474                 forwardMostMove = currentMove; // mimic old ICS behavior
18475                 return;
18476         }
18477         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18478
18479         PushInner(firstMove, lastMove);
18480         if(storedGames == 1) GreyRevert(FALSE);
18481         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18482 }
18483
18484 void
18485 PopInner (Boolean annotate)
18486 {
18487         int i, j, nrMoves;
18488         char buf[8000], moveBuf[20];
18489
18490         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18491         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18492         nrMoves = savedLast[storedGames] - currentMove;
18493         if(annotate) {
18494                 int cnt = 10;
18495                 if(!WhiteOnMove(currentMove))
18496                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18497                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18498                 for(i=currentMove; i<forwardMostMove; i++) {
18499                         if(WhiteOnMove(i))
18500                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18501                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18502                         strcat(buf, moveBuf);
18503                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18504                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18505                 }
18506                 strcat(buf, ")");
18507         }
18508         for(i=1; i<=nrMoves; i++) { // copy last variation back
18509             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18510             for(j=0; j<MOVE_LEN; j++)
18511                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18512             for(j=0; j<2*MOVE_LEN; j++)
18513                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18514             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18515             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18516             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18517             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18518             commentList[currentMove+i] = commentList[framePtr+i];
18519             commentList[framePtr+i] = NULL;
18520         }
18521         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18522         framePtr = savedFramePtr[storedGames];
18523         gameInfo.result = savedResult[storedGames];
18524         if(gameInfo.resultDetails != NULL) {
18525             free(gameInfo.resultDetails);
18526       }
18527         gameInfo.resultDetails = savedDetails[storedGames];
18528         forwardMostMove = currentMove + nrMoves;
18529 }
18530
18531 Boolean
18532 PopTail (Boolean annotate)
18533 {
18534         if(appData.icsActive) return FALSE; // only in local mode
18535         if(!storedGames) return FALSE; // sanity
18536         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18537
18538         PopInner(annotate);
18539         if(currentMove < forwardMostMove) ForwardEvent(); else
18540         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18541
18542         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18543         return TRUE;
18544 }
18545
18546 void
18547 CleanupTail ()
18548 {       // remove all shelved variations
18549         int i;
18550         for(i=0; i<storedGames; i++) {
18551             if(savedDetails[i])
18552                 free(savedDetails[i]);
18553             savedDetails[i] = NULL;
18554         }
18555         for(i=framePtr; i<MAX_MOVES; i++) {
18556                 if(commentList[i]) free(commentList[i]);
18557                 commentList[i] = NULL;
18558         }
18559         framePtr = MAX_MOVES-1;
18560         storedGames = 0;
18561 }
18562
18563 void
18564 LoadVariation (int index, char *text)
18565 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18566         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18567         int level = 0, move;
18568
18569         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18570         // first find outermost bracketing variation
18571         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18572             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18573                 if(*p == '{') wait = '}'; else
18574                 if(*p == '[') wait = ']'; else
18575                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18576                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18577             }
18578             if(*p == wait) wait = NULLCHAR; // closing ]} found
18579             p++;
18580         }
18581         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18582         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18583         end[1] = NULLCHAR; // clip off comment beyond variation
18584         ToNrEvent(currentMove-1);
18585         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18586         // kludge: use ParsePV() to append variation to game
18587         move = currentMove;
18588         ParsePV(start, TRUE, TRUE);
18589         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18590         ClearPremoveHighlights();
18591         CommentPopDown();
18592         ToNrEvent(currentMove+1);
18593 }
18594
18595 void
18596 LoadTheme ()
18597 {
18598     char *p, *q, buf[MSG_SIZ];
18599     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18600         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18601         ParseArgsFromString(buf);
18602         ActivateTheme(TRUE); // also redo colors
18603         return;
18604     }
18605     p = nickName;
18606     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18607     {
18608         int len;
18609         q = appData.themeNames;
18610         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18611       if(appData.useBitmaps) {
18612         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18613                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18614                 appData.liteBackTextureMode,
18615                 appData.darkBackTextureMode );
18616       } else {
18617         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18618                 Col2Text(2),   // lightSquareColor
18619                 Col2Text(3) ); // darkSquareColor
18620       }
18621       if(appData.useBorder) {
18622         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18623                 appData.border);
18624       } else {
18625         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18626       }
18627       if(appData.useFont) {
18628         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18629                 appData.renderPiecesWithFont,
18630                 appData.fontToPieceTable,
18631                 Col2Text(9),    // appData.fontBackColorWhite
18632                 Col2Text(10) ); // appData.fontForeColorBlack
18633       } else {
18634         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18635                 appData.pieceDirectory);
18636         if(!appData.pieceDirectory[0])
18637           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18638                 Col2Text(0),   // whitePieceColor
18639                 Col2Text(1) ); // blackPieceColor
18640       }
18641       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18642                 Col2Text(4),   // highlightSquareColor
18643                 Col2Text(5) ); // premoveHighlightColor
18644         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18645         if(insert != q) insert[-1] = NULLCHAR;
18646         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18647         if(q)   free(q);
18648     }
18649     ActivateTheme(FALSE);
18650 }