Fix promotion sweep of black Pawns in Shogi
[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         char *m = moveList[moveNum];
5145         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5146           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5147                                                m[2], m[3] - '0',
5148                                                m[5], m[6] - '0',
5149                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5150         else
5151           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5152                                                m[5], m[6] - '0',
5153                                                m[5], m[6] - '0',
5154                                                m[2], m[3] - '0');
5155           SendToProgram(buf, cps);
5156       } else
5157       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5158         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5159           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5160           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5161                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5162         } else
5163           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5164                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5165         SendToProgram(buf, cps);
5166       }
5167       else SendToProgram(moveList[moveNum], cps);
5168       /* End of additions by Tord */
5169     }
5170
5171     /* [HGM] setting up the opening has brought engine in force mode! */
5172     /*       Send 'go' if we are in a mode where machine should play. */
5173     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5174         (gameMode == TwoMachinesPlay   ||
5175 #if ZIPPY
5176          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5177 #endif
5178          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5179         SendToProgram("go\n", cps);
5180   if (appData.debugMode) {
5181     fprintf(debugFP, "(extra)\n");
5182   }
5183     }
5184     setboardSpoiledMachineBlack = 0;
5185 }
5186
5187 void
5188 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5189 {
5190     char user_move[MSG_SIZ];
5191     char suffix[4];
5192
5193     if(gameInfo.variant == VariantSChess && promoChar) {
5194         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5195         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5196     } else suffix[0] = NULLCHAR;
5197
5198     switch (moveType) {
5199       default:
5200         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5201                 (int)moveType, fromX, fromY, toX, toY);
5202         DisplayError(user_move + strlen("say "), 0);
5203         break;
5204       case WhiteKingSideCastle:
5205       case BlackKingSideCastle:
5206       case WhiteQueenSideCastleWild:
5207       case BlackQueenSideCastleWild:
5208       /* PUSH Fabien */
5209       case WhiteHSideCastleFR:
5210       case BlackHSideCastleFR:
5211       /* POP Fabien */
5212         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5213         break;
5214       case WhiteQueenSideCastle:
5215       case BlackQueenSideCastle:
5216       case WhiteKingSideCastleWild:
5217       case BlackKingSideCastleWild:
5218       /* PUSH Fabien */
5219       case WhiteASideCastleFR:
5220       case BlackASideCastleFR:
5221       /* POP Fabien */
5222         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5223         break;
5224       case WhiteNonPromotion:
5225       case BlackNonPromotion:
5226         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5227         break;
5228       case WhitePromotion:
5229       case BlackPromotion:
5230         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5231            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5232           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5233                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5234                 PieceToChar(WhiteFerz));
5235         else if(gameInfo.variant == VariantGreat)
5236           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5237                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5238                 PieceToChar(WhiteMan));
5239         else
5240           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5241                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5242                 promoChar);
5243         break;
5244       case WhiteDrop:
5245       case BlackDrop:
5246       drop:
5247         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5248                  ToUpper(PieceToChar((ChessSquare) fromX)),
5249                  AAA + toX, ONE + toY);
5250         break;
5251       case IllegalMove:  /* could be a variant we don't quite understand */
5252         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5253       case NormalMove:
5254       case WhiteCapturesEnPassant:
5255       case BlackCapturesEnPassant:
5256         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5257                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5258         break;
5259     }
5260     SendToICS(user_move);
5261     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5262         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5263 }
5264
5265 void
5266 UploadGameEvent ()
5267 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5268     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5269     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5270     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5271       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5272       return;
5273     }
5274     if(gameMode != IcsExamining) { // is this ever not the case?
5275         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5276
5277         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5278           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5279         } else { // on FICS we must first go to general examine mode
5280           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5281         }
5282         if(gameInfo.variant != VariantNormal) {
5283             // try figure out wild number, as xboard names are not always valid on ICS
5284             for(i=1; i<=36; i++) {
5285               snprintf(buf, MSG_SIZ, "wild/%d", i);
5286                 if(StringToVariant(buf) == gameInfo.variant) break;
5287             }
5288             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5289             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5290             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5291         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5292         SendToICS(ics_prefix);
5293         SendToICS(buf);
5294         if(startedFromSetupPosition || backwardMostMove != 0) {
5295           fen = PositionToFEN(backwardMostMove, NULL, 1);
5296           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5297             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5298             SendToICS(buf);
5299           } else { // FICS: everything has to set by separate bsetup commands
5300             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5301             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5302             SendToICS(buf);
5303             if(!WhiteOnMove(backwardMostMove)) {
5304                 SendToICS("bsetup tomove black\n");
5305             }
5306             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5307             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5308             SendToICS(buf);
5309             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5310             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5311             SendToICS(buf);
5312             i = boards[backwardMostMove][EP_STATUS];
5313             if(i >= 0) { // set e.p.
5314               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5315                 SendToICS(buf);
5316             }
5317             bsetup++;
5318           }
5319         }
5320       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5321             SendToICS("bsetup done\n"); // switch to normal examining.
5322     }
5323     for(i = backwardMostMove; i<last; i++) {
5324         char buf[20];
5325         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5326         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5327             int len = strlen(moveList[i]);
5328             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5329             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5330         }
5331         SendToICS(buf);
5332     }
5333     SendToICS(ics_prefix);
5334     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5335 }
5336
5337 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5338 int legNr = 1;
5339
5340 void
5341 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5342 {
5343     if (rf == DROP_RANK) {
5344       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5345       sprintf(move, "%c@%c%c\n",
5346                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5347     } else {
5348         if (promoChar == 'x' || promoChar == NULLCHAR) {
5349           sprintf(move, "%c%c%c%c\n",
5350                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5351           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5352         } else {
5353             sprintf(move, "%c%c%c%c%c\n",
5354                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5355         }
5356     }
5357 }
5358
5359 void
5360 ProcessICSInitScript (FILE *f)
5361 {
5362     char buf[MSG_SIZ];
5363
5364     while (fgets(buf, MSG_SIZ, f)) {
5365         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5366     }
5367
5368     fclose(f);
5369 }
5370
5371
5372 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5373 int dragging;
5374 static ClickType lastClickType;
5375
5376 int
5377 Partner (ChessSquare *p)
5378 { // change piece into promotion partner if one shogi-promotes to the other
5379   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5380   ChessSquare partner;
5381   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5382   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5383   *p = partner;
5384   return 1;
5385 }
5386
5387 void
5388 Sweep (int step)
5389 {
5390     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5391     static int toggleFlag;
5392     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5393     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5394     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5395     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5396     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5397     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5398     do {
5399         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5400         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5401         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5402         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5403         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5404         if(!step) step = -1;
5405     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5406             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5407             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5408             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5409     if(toX >= 0) {
5410         int victim = boards[currentMove][toY][toX];
5411         boards[currentMove][toY][toX] = promoSweep;
5412         DrawPosition(FALSE, boards[currentMove]);
5413         boards[currentMove][toY][toX] = victim;
5414     } else
5415     ChangeDragPiece(promoSweep);
5416 }
5417
5418 int
5419 PromoScroll (int x, int y)
5420 {
5421   int step = 0;
5422
5423   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5424   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5425   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5426   if(!step) return FALSE;
5427   lastX = x; lastY = y;
5428   if((promoSweep < BlackPawn) == flipView) step = -step;
5429   if(step > 0) selectFlag = 1;
5430   if(!selectFlag) Sweep(step);
5431   return FALSE;
5432 }
5433
5434 void
5435 NextPiece (int step)
5436 {
5437     ChessSquare piece = boards[currentMove][toY][toX];
5438     do {
5439         pieceSweep -= step;
5440         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5441         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5442         if(!step) step = -1;
5443     } while(PieceToChar(pieceSweep) == '.');
5444     boards[currentMove][toY][toX] = pieceSweep;
5445     DrawPosition(FALSE, boards[currentMove]);
5446     boards[currentMove][toY][toX] = piece;
5447 }
5448 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5449 void
5450 AlphaRank (char *move, int n)
5451 {
5452 //    char *p = move, c; int x, y;
5453
5454     if (appData.debugMode) {
5455         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5456     }
5457
5458     if(move[1]=='*' &&
5459        move[2]>='0' && move[2]<='9' &&
5460        move[3]>='a' && move[3]<='x'    ) {
5461         move[1] = '@';
5462         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5463         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5464     } else
5465     if(move[0]>='0' && move[0]<='9' &&
5466        move[1]>='a' && move[1]<='x' &&
5467        move[2]>='0' && move[2]<='9' &&
5468        move[3]>='a' && move[3]<='x'    ) {
5469         /* input move, Shogi -> normal */
5470         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5471         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5472         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5473         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5474     } else
5475     if(move[1]=='@' &&
5476        move[3]>='0' && move[3]<='9' &&
5477        move[2]>='a' && move[2]<='x'    ) {
5478         move[1] = '*';
5479         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5480         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5481     } else
5482     if(
5483        move[0]>='a' && move[0]<='x' &&
5484        move[3]>='0' && move[3]<='9' &&
5485        move[2]>='a' && move[2]<='x'    ) {
5486          /* output move, normal -> Shogi */
5487         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5488         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5489         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5490         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5491         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5492     }
5493     if (appData.debugMode) {
5494         fprintf(debugFP, "   out = '%s'\n", move);
5495     }
5496 }
5497
5498 char yy_textstr[8000];
5499
5500 /* Parser for moves from gnuchess, ICS, or user typein box */
5501 Boolean
5502 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5503 {
5504     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5505
5506     switch (*moveType) {
5507       case WhitePromotion:
5508       case BlackPromotion:
5509       case WhiteNonPromotion:
5510       case BlackNonPromotion:
5511       case NormalMove:
5512       case FirstLeg:
5513       case WhiteCapturesEnPassant:
5514       case BlackCapturesEnPassant:
5515       case WhiteKingSideCastle:
5516       case WhiteQueenSideCastle:
5517       case BlackKingSideCastle:
5518       case BlackQueenSideCastle:
5519       case WhiteKingSideCastleWild:
5520       case WhiteQueenSideCastleWild:
5521       case BlackKingSideCastleWild:
5522       case BlackQueenSideCastleWild:
5523       /* Code added by Tord: */
5524       case WhiteHSideCastleFR:
5525       case WhiteASideCastleFR:
5526       case BlackHSideCastleFR:
5527       case BlackASideCastleFR:
5528       /* End of code added by Tord */
5529       case IllegalMove:         /* bug or odd chess variant */
5530         if(currentMoveString[1] == '@') goto drop; // illegal drop
5531         *fromX = currentMoveString[0] - AAA;
5532         *fromY = currentMoveString[1] - ONE;
5533         *toX = currentMoveString[2] - AAA;
5534         *toY = currentMoveString[3] - ONE;
5535         *promoChar = currentMoveString[4];
5536         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5537             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5538     if (appData.debugMode) {
5539         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5540     }
5541             *fromX = *fromY = *toX = *toY = 0;
5542             return FALSE;
5543         }
5544         if (appData.testLegality) {
5545           return (*moveType != IllegalMove);
5546         } else {
5547           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5548                          // [HGM] lion: if this is a double move we are less critical
5549                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5550         }
5551
5552       case WhiteDrop:
5553       case BlackDrop:
5554       drop:
5555         *fromX = *moveType == WhiteDrop ?
5556           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5557           (int) CharToPiece(ToLower(currentMoveString[0]));
5558         *fromY = DROP_RANK;
5559         *toX = currentMoveString[2] - AAA;
5560         *toY = currentMoveString[3] - ONE;
5561         *promoChar = NULLCHAR;
5562         return TRUE;
5563
5564       case AmbiguousMove:
5565       case ImpossibleMove:
5566       case EndOfFile:
5567       case ElapsedTime:
5568       case Comment:
5569       case PGNTag:
5570       case NAG:
5571       case WhiteWins:
5572       case BlackWins:
5573       case GameIsDrawn:
5574       default:
5575     if (appData.debugMode) {
5576         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5577     }
5578         /* bug? */
5579         *fromX = *fromY = *toX = *toY = 0;
5580         *promoChar = NULLCHAR;
5581         return FALSE;
5582     }
5583 }
5584
5585 Boolean pushed = FALSE;
5586 char *lastParseAttempt;
5587
5588 void
5589 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5590 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5591   int fromX, fromY, toX, toY; char promoChar;
5592   ChessMove moveType;
5593   Boolean valid;
5594   int nr = 0;
5595
5596   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5597   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5598     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5599     pushed = TRUE;
5600   }
5601   endPV = forwardMostMove;
5602   do {
5603     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5604     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5605     lastParseAttempt = pv;
5606     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5607     if(!valid && nr == 0 &&
5608        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5609         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5610         // Hande case where played move is different from leading PV move
5611         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5612         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5613         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5614         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5615           endPV += 2; // if position different, keep this
5616           moveList[endPV-1][0] = fromX + AAA;
5617           moveList[endPV-1][1] = fromY + ONE;
5618           moveList[endPV-1][2] = toX + AAA;
5619           moveList[endPV-1][3] = toY + ONE;
5620           parseList[endPV-1][0] = NULLCHAR;
5621           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5622         }
5623       }
5624     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5625     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5626     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5627     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5628         valid++; // allow comments in PV
5629         continue;
5630     }
5631     nr++;
5632     if(endPV+1 > framePtr) break; // no space, truncate
5633     if(!valid) break;
5634     endPV++;
5635     CopyBoard(boards[endPV], boards[endPV-1]);
5636     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5637     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5638     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5639     CoordsToAlgebraic(boards[endPV - 1],
5640                              PosFlags(endPV - 1),
5641                              fromY, fromX, toY, toX, promoChar,
5642                              parseList[endPV - 1]);
5643   } while(valid);
5644   if(atEnd == 2) return; // used hidden, for PV conversion
5645   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5646   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5647   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5648                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5649   DrawPosition(TRUE, boards[currentMove]);
5650 }
5651
5652 int
5653 MultiPV (ChessProgramState *cps)
5654 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5655         int i;
5656         for(i=0; i<cps->nrOptions; i++)
5657             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5658                 return i;
5659         return -1;
5660 }
5661
5662 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5663
5664 Boolean
5665 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5666 {
5667         int startPV, multi, lineStart, origIndex = index;
5668         char *p, buf2[MSG_SIZ];
5669         ChessProgramState *cps = (pane ? &second : &first);
5670
5671         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5672         lastX = x; lastY = y;
5673         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5674         lineStart = startPV = index;
5675         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5676         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5677         index = startPV;
5678         do{ while(buf[index] && buf[index] != '\n') index++;
5679         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5680         buf[index] = 0;
5681         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5682                 int n = cps->option[multi].value;
5683                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5684                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5685                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5686                 cps->option[multi].value = n;
5687                 *start = *end = 0;
5688                 return FALSE;
5689         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5690                 ExcludeClick(origIndex - lineStart);
5691                 return FALSE;
5692         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5693                 Collapse(origIndex - lineStart);
5694                 return FALSE;
5695         }
5696         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5697         *start = startPV; *end = index-1;
5698         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5699         return TRUE;
5700 }
5701
5702 char *
5703 PvToSAN (char *pv)
5704 {
5705         static char buf[10*MSG_SIZ];
5706         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5707         *buf = NULLCHAR;
5708         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5709         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5710         for(i = forwardMostMove; i<endPV; i++){
5711             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5712             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5713             k += strlen(buf+k);
5714         }
5715         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5716         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5717         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5718         endPV = savedEnd;
5719         return buf;
5720 }
5721
5722 Boolean
5723 LoadPV (int x, int y)
5724 { // called on right mouse click to load PV
5725   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5726   lastX = x; lastY = y;
5727   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5728   extendGame = FALSE;
5729   return TRUE;
5730 }
5731
5732 void
5733 UnLoadPV ()
5734 {
5735   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5736   if(endPV < 0) return;
5737   if(appData.autoCopyPV) CopyFENToClipboard();
5738   endPV = -1;
5739   if(extendGame && currentMove > forwardMostMove) {
5740         Boolean saveAnimate = appData.animate;
5741         if(pushed) {
5742             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5743                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5744             } else storedGames--; // abandon shelved tail of original game
5745         }
5746         pushed = FALSE;
5747         forwardMostMove = currentMove;
5748         currentMove = oldFMM;
5749         appData.animate = FALSE;
5750         ToNrEvent(forwardMostMove);
5751         appData.animate = saveAnimate;
5752   }
5753   currentMove = forwardMostMove;
5754   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5755   ClearPremoveHighlights();
5756   DrawPosition(TRUE, boards[currentMove]);
5757 }
5758
5759 void
5760 MovePV (int x, int y, int h)
5761 { // step through PV based on mouse coordinates (called on mouse move)
5762   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5763
5764   // we must somehow check if right button is still down (might be released off board!)
5765   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5766   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5767   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5768   if(!step) return;
5769   lastX = x; lastY = y;
5770
5771   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5772   if(endPV < 0) return;
5773   if(y < margin) step = 1; else
5774   if(y > h - margin) step = -1;
5775   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5776   currentMove += step;
5777   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5778   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5779                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5780   DrawPosition(FALSE, boards[currentMove]);
5781 }
5782
5783
5784 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5785 // All positions will have equal probability, but the current method will not provide a unique
5786 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5787 #define DARK 1
5788 #define LITE 2
5789 #define ANY 3
5790
5791 int squaresLeft[4];
5792 int piecesLeft[(int)BlackPawn];
5793 int seed, nrOfShuffles;
5794
5795 void
5796 GetPositionNumber ()
5797 {       // sets global variable seed
5798         int i;
5799
5800         seed = appData.defaultFrcPosition;
5801         if(seed < 0) { // randomize based on time for negative FRC position numbers
5802                 for(i=0; i<50; i++) seed += random();
5803                 seed = random() ^ random() >> 8 ^ random() << 8;
5804                 if(seed<0) seed = -seed;
5805         }
5806 }
5807
5808 int
5809 put (Board board, int pieceType, int rank, int n, int shade)
5810 // put the piece on the (n-1)-th empty squares of the given shade
5811 {
5812         int i;
5813
5814         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5815                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5816                         board[rank][i] = (ChessSquare) pieceType;
5817                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5818                         squaresLeft[ANY]--;
5819                         piecesLeft[pieceType]--;
5820                         return i;
5821                 }
5822         }
5823         return -1;
5824 }
5825
5826
5827 void
5828 AddOnePiece (Board board, int pieceType, int rank, int shade)
5829 // calculate where the next piece goes, (any empty square), and put it there
5830 {
5831         int i;
5832
5833         i = seed % squaresLeft[shade];
5834         nrOfShuffles *= squaresLeft[shade];
5835         seed /= squaresLeft[shade];
5836         put(board, pieceType, rank, i, shade);
5837 }
5838
5839 void
5840 AddTwoPieces (Board board, int pieceType, int rank)
5841 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5842 {
5843         int i, n=squaresLeft[ANY], j=n-1, k;
5844
5845         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5846         i = seed % k;  // pick one
5847         nrOfShuffles *= k;
5848         seed /= k;
5849         while(i >= j) i -= j--;
5850         j = n - 1 - j; i += j;
5851         put(board, pieceType, rank, j, ANY);
5852         put(board, pieceType, rank, i, ANY);
5853 }
5854
5855 void
5856 SetUpShuffle (Board board, int number)
5857 {
5858         int i, p, first=1;
5859
5860         GetPositionNumber(); nrOfShuffles = 1;
5861
5862         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5863         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5864         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5865
5866         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5867
5868         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5869             p = (int) board[0][i];
5870             if(p < (int) BlackPawn) piecesLeft[p] ++;
5871             board[0][i] = EmptySquare;
5872         }
5873
5874         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5875             // shuffles restricted to allow normal castling put KRR first
5876             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5877                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5878             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5879                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5880             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5881                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5882             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5883                 put(board, WhiteRook, 0, 0, ANY);
5884             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5885         }
5886
5887         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5888             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5889             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5890                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5891                 while(piecesLeft[p] >= 2) {
5892                     AddOnePiece(board, p, 0, LITE);
5893                     AddOnePiece(board, p, 0, DARK);
5894                 }
5895                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5896             }
5897
5898         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5899             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5900             // but we leave King and Rooks for last, to possibly obey FRC restriction
5901             if(p == (int)WhiteRook) continue;
5902             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5903             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5904         }
5905
5906         // now everything is placed, except perhaps King (Unicorn) and Rooks
5907
5908         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5909             // Last King gets castling rights
5910             while(piecesLeft[(int)WhiteUnicorn]) {
5911                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5912                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5913             }
5914
5915             while(piecesLeft[(int)WhiteKing]) {
5916                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5917                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5918             }
5919
5920
5921         } else {
5922             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5923             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5924         }
5925
5926         // Only Rooks can be left; simply place them all
5927         while(piecesLeft[(int)WhiteRook]) {
5928                 i = put(board, WhiteRook, 0, 0, ANY);
5929                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5930                         if(first) {
5931                                 first=0;
5932                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5933                         }
5934                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5935                 }
5936         }
5937         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5938             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5939         }
5940
5941         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5942 }
5943
5944 int
5945 SetCharTable (char *table, const char * map)
5946 /* [HGM] moved here from winboard.c because of its general usefulness */
5947 /*       Basically a safe strcpy that uses the last character as King */
5948 {
5949     int result = FALSE; int NrPieces;
5950
5951     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5952                     && NrPieces >= 12 && !(NrPieces&1)) {
5953         int i; /* [HGM] Accept even length from 12 to 34 */
5954
5955         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5956         for( i=0; i<NrPieces/2-1; i++ ) {
5957             table[i] = map[i];
5958             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5959         }
5960         table[(int) WhiteKing]  = map[NrPieces/2-1];
5961         table[(int) BlackKing]  = map[NrPieces-1];
5962
5963         result = TRUE;
5964     }
5965
5966     return result;
5967 }
5968
5969 void
5970 Prelude (Board board)
5971 {       // [HGM] superchess: random selection of exo-pieces
5972         int i, j, k; ChessSquare p;
5973         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5974
5975         GetPositionNumber(); // use FRC position number
5976
5977         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5978             SetCharTable(pieceToChar, appData.pieceToCharTable);
5979             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5980                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5981         }
5982
5983         j = seed%4;                 seed /= 4;
5984         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5985         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5986         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5987         j = seed%3 + (seed%3 >= j); seed /= 3;
5988         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5989         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5990         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5991         j = seed%3;                 seed /= 3;
5992         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5993         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5994         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5995         j = seed%2 + (seed%2 >= j); seed /= 2;
5996         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5997         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5998         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5999         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6000         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6001         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6002         put(board, exoPieces[0],    0, 0, ANY);
6003         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6004 }
6005
6006 void
6007 InitPosition (int redraw)
6008 {
6009     ChessSquare (* pieces)[BOARD_FILES];
6010     int i, j, pawnRow=1, pieceRows=1, overrule,
6011     oldx = gameInfo.boardWidth,
6012     oldy = gameInfo.boardHeight,
6013     oldh = gameInfo.holdingsWidth;
6014     static int oldv;
6015
6016     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6017
6018     /* [AS] Initialize pv info list [HGM] and game status */
6019     {
6020         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6021             pvInfoList[i].depth = 0;
6022             boards[i][EP_STATUS] = EP_NONE;
6023             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6024         }
6025
6026         initialRulePlies = 0; /* 50-move counter start */
6027
6028         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6029         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6030     }
6031
6032
6033     /* [HGM] logic here is completely changed. In stead of full positions */
6034     /* the initialized data only consist of the two backranks. The switch */
6035     /* selects which one we will use, which is than copied to the Board   */
6036     /* initialPosition, which for the rest is initialized by Pawns and    */
6037     /* empty squares. This initial position is then copied to boards[0],  */
6038     /* possibly after shuffling, so that it remains available.            */
6039
6040     gameInfo.holdingsWidth = 0; /* default board sizes */
6041     gameInfo.boardWidth    = 8;
6042     gameInfo.boardHeight   = 8;
6043     gameInfo.holdingsSize  = 0;
6044     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6045     for(i=0; i<BOARD_FILES-6; i++)
6046       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6047     initialPosition[EP_STATUS] = EP_NONE;
6048     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6049     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6050     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6051          SetCharTable(pieceNickName, appData.pieceNickNames);
6052     else SetCharTable(pieceNickName, "............");
6053     pieces = FIDEArray;
6054
6055     switch (gameInfo.variant) {
6056     case VariantFischeRandom:
6057       shuffleOpenings = TRUE;
6058       appData.fischerCastling = TRUE;
6059     default:
6060       break;
6061     case VariantShatranj:
6062       pieces = ShatranjArray;
6063       nrCastlingRights = 0;
6064       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6065       break;
6066     case VariantMakruk:
6067       pieces = makrukArray;
6068       nrCastlingRights = 0;
6069       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6070       break;
6071     case VariantASEAN:
6072       pieces = aseanArray;
6073       nrCastlingRights = 0;
6074       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6075       break;
6076     case VariantTwoKings:
6077       pieces = twoKingsArray;
6078       break;
6079     case VariantGrand:
6080       pieces = GrandArray;
6081       nrCastlingRights = 0;
6082       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6083       gameInfo.boardWidth = 10;
6084       gameInfo.boardHeight = 10;
6085       gameInfo.holdingsSize = 7;
6086       break;
6087     case VariantCapaRandom:
6088       shuffleOpenings = TRUE;
6089       appData.fischerCastling = TRUE;
6090     case VariantCapablanca:
6091       pieces = CapablancaArray;
6092       gameInfo.boardWidth = 10;
6093       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6094       break;
6095     case VariantGothic:
6096       pieces = GothicArray;
6097       gameInfo.boardWidth = 10;
6098       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6099       break;
6100     case VariantSChess:
6101       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6102       gameInfo.holdingsSize = 7;
6103       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6104       break;
6105     case VariantJanus:
6106       pieces = JanusArray;
6107       gameInfo.boardWidth = 10;
6108       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6109       nrCastlingRights = 6;
6110         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6111         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6112         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6113         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6114         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6115         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6116       break;
6117     case VariantFalcon:
6118       pieces = FalconArray;
6119       gameInfo.boardWidth = 10;
6120       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6121       break;
6122     case VariantXiangqi:
6123       pieces = XiangqiArray;
6124       gameInfo.boardWidth  = 9;
6125       gameInfo.boardHeight = 10;
6126       nrCastlingRights = 0;
6127       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6128       break;
6129     case VariantShogi:
6130       pieces = ShogiArray;
6131       gameInfo.boardWidth  = 9;
6132       gameInfo.boardHeight = 9;
6133       gameInfo.holdingsSize = 7;
6134       nrCastlingRights = 0;
6135       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6136       break;
6137     case VariantChu:
6138       pieces = ChuArray; pieceRows = 3;
6139       gameInfo.boardWidth  = 12;
6140       gameInfo.boardHeight = 12;
6141       nrCastlingRights = 0;
6142       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6143                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6144       break;
6145     case VariantCourier:
6146       pieces = CourierArray;
6147       gameInfo.boardWidth  = 12;
6148       nrCastlingRights = 0;
6149       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6150       break;
6151     case VariantKnightmate:
6152       pieces = KnightmateArray;
6153       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6154       break;
6155     case VariantSpartan:
6156       pieces = SpartanArray;
6157       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6158       break;
6159     case VariantLion:
6160       pieces = lionArray;
6161       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6162       break;
6163     case VariantChuChess:
6164       pieces = ChuChessArray;
6165       gameInfo.boardWidth = 10;
6166       gameInfo.boardHeight = 10;
6167       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6168       break;
6169     case VariantFairy:
6170       pieces = fairyArray;
6171       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6172       break;
6173     case VariantGreat:
6174       pieces = GreatArray;
6175       gameInfo.boardWidth = 10;
6176       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6177       gameInfo.holdingsSize = 8;
6178       break;
6179     case VariantSuper:
6180       pieces = FIDEArray;
6181       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6182       gameInfo.holdingsSize = 8;
6183       startedFromSetupPosition = TRUE;
6184       break;
6185     case VariantCrazyhouse:
6186     case VariantBughouse:
6187       pieces = FIDEArray;
6188       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6189       gameInfo.holdingsSize = 5;
6190       break;
6191     case VariantWildCastle:
6192       pieces = FIDEArray;
6193       /* !!?shuffle with kings guaranteed to be on d or e file */
6194       shuffleOpenings = 1;
6195       break;
6196     case VariantNoCastle:
6197       pieces = FIDEArray;
6198       nrCastlingRights = 0;
6199       /* !!?unconstrained back-rank shuffle */
6200       shuffleOpenings = 1;
6201       break;
6202     }
6203
6204     overrule = 0;
6205     if(appData.NrFiles >= 0) {
6206         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6207         gameInfo.boardWidth = appData.NrFiles;
6208     }
6209     if(appData.NrRanks >= 0) {
6210         gameInfo.boardHeight = appData.NrRanks;
6211     }
6212     if(appData.holdingsSize >= 0) {
6213         i = appData.holdingsSize;
6214         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6215         gameInfo.holdingsSize = i;
6216     }
6217     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6218     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6219         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6220
6221     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6222     if(pawnRow < 1) pawnRow = 1;
6223     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6224        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6225     if(gameInfo.variant == VariantChu) pawnRow = 3;
6226
6227     /* User pieceToChar list overrules defaults */
6228     if(appData.pieceToCharTable != NULL)
6229         SetCharTable(pieceToChar, appData.pieceToCharTable);
6230
6231     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6232
6233         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6234             s = (ChessSquare) 0; /* account holding counts in guard band */
6235         for( i=0; i<BOARD_HEIGHT; i++ )
6236             initialPosition[i][j] = s;
6237
6238         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6239         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6240         initialPosition[pawnRow][j] = WhitePawn;
6241         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6242         if(gameInfo.variant == VariantXiangqi) {
6243             if(j&1) {
6244                 initialPosition[pawnRow][j] =
6245                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6246                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6247                    initialPosition[2][j] = WhiteCannon;
6248                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6249                 }
6250             }
6251         }
6252         if(gameInfo.variant == VariantChu) {
6253              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6254                initialPosition[pawnRow+1][j] = WhiteCobra,
6255                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6256              for(i=1; i<pieceRows; i++) {
6257                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6258                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6259              }
6260         }
6261         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6262             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6263                initialPosition[0][j] = WhiteRook;
6264                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6265             }
6266         }
6267         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6268     }
6269     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6270     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6271
6272             j=BOARD_LEFT+1;
6273             initialPosition[1][j] = WhiteBishop;
6274             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6275             j=BOARD_RGHT-2;
6276             initialPosition[1][j] = WhiteRook;
6277             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6278     }
6279
6280     if( nrCastlingRights == -1) {
6281         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6282         /*       This sets default castling rights from none to normal corners   */
6283         /* Variants with other castling rights must set them themselves above    */
6284         nrCastlingRights = 6;
6285
6286         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6287         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6288         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6289         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6290         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6291         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6292      }
6293
6294      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6295      if(gameInfo.variant == VariantGreat) { // promotion commoners
6296         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6297         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6298         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6299         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6300      }
6301      if( gameInfo.variant == VariantSChess ) {
6302       initialPosition[1][0] = BlackMarshall;
6303       initialPosition[2][0] = BlackAngel;
6304       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6305       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6306       initialPosition[1][1] = initialPosition[2][1] =
6307       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6308      }
6309   if (appData.debugMode) {
6310     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6311   }
6312     if(shuffleOpenings) {
6313         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6314         startedFromSetupPosition = TRUE;
6315     }
6316     if(startedFromPositionFile) {
6317       /* [HGM] loadPos: use PositionFile for every new game */
6318       CopyBoard(initialPosition, filePosition);
6319       for(i=0; i<nrCastlingRights; i++)
6320           initialRights[i] = filePosition[CASTLING][i];
6321       startedFromSetupPosition = TRUE;
6322     }
6323
6324     CopyBoard(boards[0], initialPosition);
6325
6326     if(oldx != gameInfo.boardWidth ||
6327        oldy != gameInfo.boardHeight ||
6328        oldv != gameInfo.variant ||
6329        oldh != gameInfo.holdingsWidth
6330                                          )
6331             InitDrawingSizes(-2 ,0);
6332
6333     oldv = gameInfo.variant;
6334     if (redraw)
6335       DrawPosition(TRUE, boards[currentMove]);
6336 }
6337
6338 void
6339 SendBoard (ChessProgramState *cps, int moveNum)
6340 {
6341     char message[MSG_SIZ];
6342
6343     if (cps->useSetboard) {
6344       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6345       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6346       SendToProgram(message, cps);
6347       free(fen);
6348
6349     } else {
6350       ChessSquare *bp;
6351       int i, j, left=0, right=BOARD_WIDTH;
6352       /* Kludge to set black to move, avoiding the troublesome and now
6353        * deprecated "black" command.
6354        */
6355       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6356         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6357
6358       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6359
6360       SendToProgram("edit\n", cps);
6361       SendToProgram("#\n", cps);
6362       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6363         bp = &boards[moveNum][i][left];
6364         for (j = left; j < right; j++, bp++) {
6365           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6366           if ((int) *bp < (int) BlackPawn) {
6367             if(j == BOARD_RGHT+1)
6368                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6369             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6370             if(message[0] == '+' || message[0] == '~') {
6371               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6372                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6373                         AAA + j, ONE + i);
6374             }
6375             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6376                 message[1] = BOARD_RGHT   - 1 - j + '1';
6377                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6378             }
6379             SendToProgram(message, cps);
6380           }
6381         }
6382       }
6383
6384       SendToProgram("c\n", cps);
6385       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6386         bp = &boards[moveNum][i][left];
6387         for (j = left; j < right; j++, bp++) {
6388           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6389           if (((int) *bp != (int) EmptySquare)
6390               && ((int) *bp >= (int) BlackPawn)) {
6391             if(j == BOARD_LEFT-2)
6392                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6393             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6394                     AAA + j, ONE + i);
6395             if(message[0] == '+' || message[0] == '~') {
6396               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6397                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6398                         AAA + j, ONE + i);
6399             }
6400             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6401                 message[1] = BOARD_RGHT   - 1 - j + '1';
6402                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6403             }
6404             SendToProgram(message, cps);
6405           }
6406         }
6407       }
6408
6409       SendToProgram(".\n", cps);
6410     }
6411     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6412 }
6413
6414 char exclusionHeader[MSG_SIZ];
6415 int exCnt, excludePtr;
6416 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6417 static Exclusion excluTab[200];
6418 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6419
6420 static void
6421 WriteMap (int s)
6422 {
6423     int j;
6424     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6425     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6426 }
6427
6428 static void
6429 ClearMap ()
6430 {
6431     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6432     excludePtr = 24; exCnt = 0;
6433     WriteMap(0);
6434 }
6435
6436 static void
6437 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6438 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6439     char buf[2*MOVE_LEN], *p;
6440     Exclusion *e = excluTab;
6441     int i;
6442     for(i=0; i<exCnt; i++)
6443         if(e[i].ff == fromX && e[i].fr == fromY &&
6444            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6445     if(i == exCnt) { // was not in exclude list; add it
6446         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6447         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6448             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6449             return; // abort
6450         }
6451         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6452         excludePtr++; e[i].mark = excludePtr++;
6453         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6454         exCnt++;
6455     }
6456     exclusionHeader[e[i].mark] = state;
6457 }
6458
6459 static int
6460 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6461 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6462     char buf[MSG_SIZ];
6463     int j, k;
6464     ChessMove moveType;
6465     if((signed char)promoChar == -1) { // kludge to indicate best move
6466         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6467             return 1; // if unparsable, abort
6468     }
6469     // update exclusion map (resolving toggle by consulting existing state)
6470     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6471     j = k%8; k >>= 3;
6472     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6473     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6474          excludeMap[k] |=   1<<j;
6475     else excludeMap[k] &= ~(1<<j);
6476     // update header
6477     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6478     // inform engine
6479     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6480     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6481     SendToBoth(buf);
6482     return (state == '+');
6483 }
6484
6485 static void
6486 ExcludeClick (int index)
6487 {
6488     int i, j;
6489     Exclusion *e = excluTab;
6490     if(index < 25) { // none, best or tail clicked
6491         if(index < 13) { // none: include all
6492             WriteMap(0); // clear map
6493             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6494             SendToBoth("include all\n"); // and inform engine
6495         } else if(index > 18) { // tail
6496             if(exclusionHeader[19] == '-') { // tail was excluded
6497                 SendToBoth("include all\n");
6498                 WriteMap(0); // clear map completely
6499                 // now re-exclude selected moves
6500                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6501                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6502             } else { // tail was included or in mixed state
6503                 SendToBoth("exclude all\n");
6504                 WriteMap(0xFF); // fill map completely
6505                 // now re-include selected moves
6506                 j = 0; // count them
6507                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6508                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6509                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6510             }
6511         } else { // best
6512             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6513         }
6514     } else {
6515         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6516             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6517             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6518             break;
6519         }
6520     }
6521 }
6522
6523 ChessSquare
6524 DefaultPromoChoice (int white)
6525 {
6526     ChessSquare result;
6527     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6528        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6529         result = WhiteFerz; // no choice
6530     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6531         result= WhiteKing; // in Suicide Q is the last thing we want
6532     else if(gameInfo.variant == VariantSpartan)
6533         result = white ? WhiteQueen : WhiteAngel;
6534     else result = WhiteQueen;
6535     if(!white) result = WHITE_TO_BLACK result;
6536     return result;
6537 }
6538
6539 static int autoQueen; // [HGM] oneclick
6540
6541 int
6542 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6543 {
6544     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6545     /* [HGM] add Shogi promotions */
6546     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6547     ChessSquare piece, partner;
6548     ChessMove moveType;
6549     Boolean premove;
6550
6551     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6552     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6553
6554     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6555       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6556         return FALSE;
6557
6558     piece = boards[currentMove][fromY][fromX];
6559     if(gameInfo.variant == VariantChu) {
6560         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6561         promotionZoneSize = BOARD_HEIGHT/3;
6562         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6563     } else if(gameInfo.variant == VariantShogi) {
6564         promotionZoneSize = BOARD_HEIGHT/3;
6565         highestPromotingPiece = (int)WhiteAlfil;
6566     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6567         promotionZoneSize = 3;
6568     }
6569
6570     // Treat Lance as Pawn when it is not representing Amazon or Lance
6571     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6572         if(piece == WhiteLance) piece = WhitePawn; else
6573         if(piece == BlackLance) piece = BlackPawn;
6574     }
6575
6576     // next weed out all moves that do not touch the promotion zone at all
6577     if((int)piece >= BlackPawn) {
6578         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6579              return FALSE;
6580         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6581         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6582     } else {
6583         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6584            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6585         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6586              return FALSE;
6587     }
6588
6589     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6590
6591     // weed out mandatory Shogi promotions
6592     if(gameInfo.variant == VariantShogi) {
6593         if(piece >= BlackPawn) {
6594             if(toY == 0 && piece == BlackPawn ||
6595                toY == 0 && piece == BlackQueen ||
6596                toY <= 1 && piece == BlackKnight) {
6597                 *promoChoice = '+';
6598                 return FALSE;
6599             }
6600         } else {
6601             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6602                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6603                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6604                 *promoChoice = '+';
6605                 return FALSE;
6606             }
6607         }
6608     }
6609
6610     // weed out obviously illegal Pawn moves
6611     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6612         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6613         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6614         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6615         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6616         // note we are not allowed to test for valid (non-)capture, due to premove
6617     }
6618
6619     // we either have a choice what to promote to, or (in Shogi) whether to promote
6620     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6621        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6622         ChessSquare p=BlackFerz;  // no choice
6623         while(p < EmptySquare) {  //but make sure we use piece that exists
6624             *promoChoice = PieceToChar(p++);
6625             if(*promoChoice != '.') break;
6626         }
6627         return FALSE;
6628     }
6629     // no sense asking what we must promote to if it is going to explode...
6630     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6631         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6632         return FALSE;
6633     }
6634     // give caller the default choice even if we will not make it
6635     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6636     partner = piece; // pieces can promote if the pieceToCharTable says so
6637     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6638     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6639     if(        sweepSelect && gameInfo.variant != VariantGreat
6640                            && gameInfo.variant != VariantGrand
6641                            && gameInfo.variant != VariantSuper) return FALSE;
6642     if(autoQueen) return FALSE; // predetermined
6643
6644     // suppress promotion popup on illegal moves that are not premoves
6645     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6646               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6647     if(appData.testLegality && !premove) {
6648         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6649                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6650         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6651         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6652             return FALSE;
6653     }
6654
6655     return TRUE;
6656 }
6657
6658 int
6659 InPalace (int row, int column)
6660 {   /* [HGM] for Xiangqi */
6661     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6662          column < (BOARD_WIDTH + 4)/2 &&
6663          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6664     return FALSE;
6665 }
6666
6667 int
6668 PieceForSquare (int x, int y)
6669 {
6670   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6671      return -1;
6672   else
6673      return boards[currentMove][y][x];
6674 }
6675
6676 int
6677 OKToStartUserMove (int x, int y)
6678 {
6679     ChessSquare from_piece;
6680     int white_piece;
6681
6682     if (matchMode) return FALSE;
6683     if (gameMode == EditPosition) return TRUE;
6684
6685     if (x >= 0 && y >= 0)
6686       from_piece = boards[currentMove][y][x];
6687     else
6688       from_piece = EmptySquare;
6689
6690     if (from_piece == EmptySquare) return FALSE;
6691
6692     white_piece = (int)from_piece >= (int)WhitePawn &&
6693       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6694
6695     switch (gameMode) {
6696       case AnalyzeFile:
6697       case TwoMachinesPlay:
6698       case EndOfGame:
6699         return FALSE;
6700
6701       case IcsObserving:
6702       case IcsIdle:
6703         return FALSE;
6704
6705       case MachinePlaysWhite:
6706       case IcsPlayingBlack:
6707         if (appData.zippyPlay) return FALSE;
6708         if (white_piece) {
6709             DisplayMoveError(_("You are playing Black"));
6710             return FALSE;
6711         }
6712         break;
6713
6714       case MachinePlaysBlack:
6715       case IcsPlayingWhite:
6716         if (appData.zippyPlay) return FALSE;
6717         if (!white_piece) {
6718             DisplayMoveError(_("You are playing White"));
6719             return FALSE;
6720         }
6721         break;
6722
6723       case PlayFromGameFile:
6724             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6725       case EditGame:
6726         if (!white_piece && WhiteOnMove(currentMove)) {
6727             DisplayMoveError(_("It is White's turn"));
6728             return FALSE;
6729         }
6730         if (white_piece && !WhiteOnMove(currentMove)) {
6731             DisplayMoveError(_("It is Black's turn"));
6732             return FALSE;
6733         }
6734         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6735             /* Editing correspondence game history */
6736             /* Could disallow this or prompt for confirmation */
6737             cmailOldMove = -1;
6738         }
6739         break;
6740
6741       case BeginningOfGame:
6742         if (appData.icsActive) return FALSE;
6743         if (!appData.noChessProgram) {
6744             if (!white_piece) {
6745                 DisplayMoveError(_("You are playing White"));
6746                 return FALSE;
6747             }
6748         }
6749         break;
6750
6751       case Training:
6752         if (!white_piece && WhiteOnMove(currentMove)) {
6753             DisplayMoveError(_("It is White's turn"));
6754             return FALSE;
6755         }
6756         if (white_piece && !WhiteOnMove(currentMove)) {
6757             DisplayMoveError(_("It is Black's turn"));
6758             return FALSE;
6759         }
6760         break;
6761
6762       default:
6763       case IcsExamining:
6764         break;
6765     }
6766     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6767         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6768         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6769         && gameMode != AnalyzeFile && gameMode != Training) {
6770         DisplayMoveError(_("Displayed position is not current"));
6771         return FALSE;
6772     }
6773     return TRUE;
6774 }
6775
6776 Boolean
6777 OnlyMove (int *x, int *y, Boolean captures)
6778 {
6779     DisambiguateClosure cl;
6780     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6781     switch(gameMode) {
6782       case MachinePlaysBlack:
6783       case IcsPlayingWhite:
6784       case BeginningOfGame:
6785         if(!WhiteOnMove(currentMove)) return FALSE;
6786         break;
6787       case MachinePlaysWhite:
6788       case IcsPlayingBlack:
6789         if(WhiteOnMove(currentMove)) return FALSE;
6790         break;
6791       case EditGame:
6792         break;
6793       default:
6794         return FALSE;
6795     }
6796     cl.pieceIn = EmptySquare;
6797     cl.rfIn = *y;
6798     cl.ffIn = *x;
6799     cl.rtIn = -1;
6800     cl.ftIn = -1;
6801     cl.promoCharIn = NULLCHAR;
6802     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6803     if( cl.kind == NormalMove ||
6804         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6805         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6806         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6807       fromX = cl.ff;
6808       fromY = cl.rf;
6809       *x = cl.ft;
6810       *y = cl.rt;
6811       return TRUE;
6812     }
6813     if(cl.kind != ImpossibleMove) return FALSE;
6814     cl.pieceIn = EmptySquare;
6815     cl.rfIn = -1;
6816     cl.ffIn = -1;
6817     cl.rtIn = *y;
6818     cl.ftIn = *x;
6819     cl.promoCharIn = NULLCHAR;
6820     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6821     if( cl.kind == NormalMove ||
6822         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6823         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6824         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6825       fromX = cl.ff;
6826       fromY = cl.rf;
6827       *x = cl.ft;
6828       *y = cl.rt;
6829       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6830       return TRUE;
6831     }
6832     return FALSE;
6833 }
6834
6835 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6836 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6837 int lastLoadGameUseList = FALSE;
6838 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6839 ChessMove lastLoadGameStart = EndOfFile;
6840 int doubleClick;
6841 Boolean addToBookFlag;
6842
6843 void
6844 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6845 {
6846     ChessMove moveType;
6847     ChessSquare pup;
6848     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6849
6850     /* Check if the user is playing in turn.  This is complicated because we
6851        let the user "pick up" a piece before it is his turn.  So the piece he
6852        tried to pick up may have been captured by the time he puts it down!
6853        Therefore we use the color the user is supposed to be playing in this
6854        test, not the color of the piece that is currently on the starting
6855        square---except in EditGame mode, where the user is playing both
6856        sides; fortunately there the capture race can't happen.  (It can
6857        now happen in IcsExamining mode, but that's just too bad.  The user
6858        will get a somewhat confusing message in that case.)
6859        */
6860
6861     switch (gameMode) {
6862       case AnalyzeFile:
6863       case TwoMachinesPlay:
6864       case EndOfGame:
6865       case IcsObserving:
6866       case IcsIdle:
6867         /* We switched into a game mode where moves are not accepted,
6868            perhaps while the mouse button was down. */
6869         return;
6870
6871       case MachinePlaysWhite:
6872         /* User is moving for Black */
6873         if (WhiteOnMove(currentMove)) {
6874             DisplayMoveError(_("It is White's turn"));
6875             return;
6876         }
6877         break;
6878
6879       case MachinePlaysBlack:
6880         /* User is moving for White */
6881         if (!WhiteOnMove(currentMove)) {
6882             DisplayMoveError(_("It is Black's turn"));
6883             return;
6884         }
6885         break;
6886
6887       case PlayFromGameFile:
6888             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6889       case EditGame:
6890       case IcsExamining:
6891       case BeginningOfGame:
6892       case AnalyzeMode:
6893       case Training:
6894         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6895         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6896             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6897             /* User is moving for Black */
6898             if (WhiteOnMove(currentMove)) {
6899                 DisplayMoveError(_("It is White's turn"));
6900                 return;
6901             }
6902         } else {
6903             /* User is moving for White */
6904             if (!WhiteOnMove(currentMove)) {
6905                 DisplayMoveError(_("It is Black's turn"));
6906                 return;
6907             }
6908         }
6909         break;
6910
6911       case IcsPlayingBlack:
6912         /* User is moving for Black */
6913         if (WhiteOnMove(currentMove)) {
6914             if (!appData.premove) {
6915                 DisplayMoveError(_("It is White's turn"));
6916             } else if (toX >= 0 && toY >= 0) {
6917                 premoveToX = toX;
6918                 premoveToY = toY;
6919                 premoveFromX = fromX;
6920                 premoveFromY = fromY;
6921                 premovePromoChar = promoChar;
6922                 gotPremove = 1;
6923                 if (appData.debugMode)
6924                     fprintf(debugFP, "Got premove: fromX %d,"
6925                             "fromY %d, toX %d, toY %d\n",
6926                             fromX, fromY, toX, toY);
6927             }
6928             return;
6929         }
6930         break;
6931
6932       case IcsPlayingWhite:
6933         /* User is moving for White */
6934         if (!WhiteOnMove(currentMove)) {
6935             if (!appData.premove) {
6936                 DisplayMoveError(_("It is Black's turn"));
6937             } else if (toX >= 0 && toY >= 0) {
6938                 premoveToX = toX;
6939                 premoveToY = toY;
6940                 premoveFromX = fromX;
6941                 premoveFromY = fromY;
6942                 premovePromoChar = promoChar;
6943                 gotPremove = 1;
6944                 if (appData.debugMode)
6945                     fprintf(debugFP, "Got premove: fromX %d,"
6946                             "fromY %d, toX %d, toY %d\n",
6947                             fromX, fromY, toX, toY);
6948             }
6949             return;
6950         }
6951         break;
6952
6953       default:
6954         break;
6955
6956       case EditPosition:
6957         /* EditPosition, empty square, or different color piece;
6958            click-click move is possible */
6959         if (toX == -2 || toY == -2) {
6960             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6961             DrawPosition(FALSE, boards[currentMove]);
6962             return;
6963         } else if (toX >= 0 && toY >= 0) {
6964             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6965                 ChessSquare q, p = boards[0][rf][ff];
6966                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6967                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6968                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6969                 if(PieceToChar(q) == '+') gatingPiece = p;
6970             }
6971             boards[0][toY][toX] = boards[0][fromY][fromX];
6972             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6973                 if(boards[0][fromY][0] != EmptySquare) {
6974                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6975                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6976                 }
6977             } else
6978             if(fromX == BOARD_RGHT+1) {
6979                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6980                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6981                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6982                 }
6983             } else
6984             boards[0][fromY][fromX] = gatingPiece;
6985             DrawPosition(FALSE, boards[currentMove]);
6986             return;
6987         }
6988         return;
6989     }
6990
6991     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6992     pup = boards[currentMove][toY][toX];
6993
6994     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6995     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6996          if( pup != EmptySquare ) return;
6997          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6998            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6999                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7000            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7001            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7002            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7003            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7004          fromY = DROP_RANK;
7005     }
7006
7007     /* [HGM] always test for legality, to get promotion info */
7008     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7009                                          fromY, fromX, toY, toX, promoChar);
7010
7011     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7012
7013     /* [HGM] but possibly ignore an IllegalMove result */
7014     if (appData.testLegality) {
7015         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7016             DisplayMoveError(_("Illegal move"));
7017             return;
7018         }
7019     }
7020
7021     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7022         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7023              ClearPremoveHighlights(); // was included
7024         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7025         return;
7026     }
7027
7028     if(addToBookFlag) { // adding moves to book
7029         char buf[MSG_SIZ], move[MSG_SIZ];
7030         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7031         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7032         AddBookMove(buf);
7033         addToBookFlag = FALSE;
7034         ClearHighlights();
7035         return;
7036     }
7037
7038     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7039 }
7040
7041 /* Common tail of UserMoveEvent and DropMenuEvent */
7042 int
7043 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7044 {
7045     char *bookHit = 0;
7046
7047     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7048         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7049         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7050         if(WhiteOnMove(currentMove)) {
7051             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7052         } else {
7053             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7054         }
7055     }
7056
7057     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7058        move type in caller when we know the move is a legal promotion */
7059     if(moveType == NormalMove && promoChar)
7060         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7061
7062     /* [HGM] <popupFix> The following if has been moved here from
7063        UserMoveEvent(). Because it seemed to belong here (why not allow
7064        piece drops in training games?), and because it can only be
7065        performed after it is known to what we promote. */
7066     if (gameMode == Training) {
7067       /* compare the move played on the board to the next move in the
7068        * game. If they match, display the move and the opponent's response.
7069        * If they don't match, display an error message.
7070        */
7071       int saveAnimate;
7072       Board testBoard;
7073       CopyBoard(testBoard, boards[currentMove]);
7074       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7075
7076       if (CompareBoards(testBoard, boards[currentMove+1])) {
7077         ForwardInner(currentMove+1);
7078
7079         /* Autoplay the opponent's response.
7080          * if appData.animate was TRUE when Training mode was entered,
7081          * the response will be animated.
7082          */
7083         saveAnimate = appData.animate;
7084         appData.animate = animateTraining;
7085         ForwardInner(currentMove+1);
7086         appData.animate = saveAnimate;
7087
7088         /* check for the end of the game */
7089         if (currentMove >= forwardMostMove) {
7090           gameMode = PlayFromGameFile;
7091           ModeHighlight();
7092           SetTrainingModeOff();
7093           DisplayInformation(_("End of game"));
7094         }
7095       } else {
7096         DisplayError(_("Incorrect move"), 0);
7097       }
7098       return 1;
7099     }
7100
7101   /* Ok, now we know that the move is good, so we can kill
7102      the previous line in Analysis Mode */
7103   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7104                                 && currentMove < forwardMostMove) {
7105     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7106     else forwardMostMove = currentMove;
7107   }
7108
7109   ClearMap();
7110
7111   /* If we need the chess program but it's dead, restart it */
7112   ResurrectChessProgram();
7113
7114   /* A user move restarts a paused game*/
7115   if (pausing)
7116     PauseEvent();
7117
7118   thinkOutput[0] = NULLCHAR;
7119
7120   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7121
7122   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7123     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7124     return 1;
7125   }
7126
7127   if (gameMode == BeginningOfGame) {
7128     if (appData.noChessProgram) {
7129       gameMode = EditGame;
7130       SetGameInfo();
7131     } else {
7132       char buf[MSG_SIZ];
7133       gameMode = MachinePlaysBlack;
7134       StartClocks();
7135       SetGameInfo();
7136       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7137       DisplayTitle(buf);
7138       if (first.sendName) {
7139         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7140         SendToProgram(buf, &first);
7141       }
7142       StartClocks();
7143     }
7144     ModeHighlight();
7145   }
7146
7147   /* Relay move to ICS or chess engine */
7148   if (appData.icsActive) {
7149     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7150         gameMode == IcsExamining) {
7151       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7152         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7153         SendToICS("draw ");
7154         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7155       }
7156       // also send plain move, in case ICS does not understand atomic claims
7157       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7158       ics_user_moved = 1;
7159     }
7160   } else {
7161     if (first.sendTime && (gameMode == BeginningOfGame ||
7162                            gameMode == MachinePlaysWhite ||
7163                            gameMode == MachinePlaysBlack)) {
7164       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7165     }
7166     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7167          // [HGM] book: if program might be playing, let it use book
7168         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7169         first.maybeThinking = TRUE;
7170     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7171         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7172         SendBoard(&first, currentMove+1);
7173         if(second.analyzing) {
7174             if(!second.useSetboard) SendToProgram("undo\n", &second);
7175             SendBoard(&second, currentMove+1);
7176         }
7177     } else {
7178         SendMoveToProgram(forwardMostMove-1, &first);
7179         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7180     }
7181     if (currentMove == cmailOldMove + 1) {
7182       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7183     }
7184   }
7185
7186   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7187
7188   switch (gameMode) {
7189   case EditGame:
7190     if(appData.testLegality)
7191     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7192     case MT_NONE:
7193     case MT_CHECK:
7194       break;
7195     case MT_CHECKMATE:
7196     case MT_STAINMATE:
7197       if (WhiteOnMove(currentMove)) {
7198         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7199       } else {
7200         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7201       }
7202       break;
7203     case MT_STALEMATE:
7204       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7205       break;
7206     }
7207     break;
7208
7209   case MachinePlaysBlack:
7210   case MachinePlaysWhite:
7211     /* disable certain menu options while machine is thinking */
7212     SetMachineThinkingEnables();
7213     break;
7214
7215   default:
7216     break;
7217   }
7218
7219   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7220   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7221
7222   if(bookHit) { // [HGM] book: simulate book reply
7223         static char bookMove[MSG_SIZ]; // a bit generous?
7224
7225         programStats.nodes = programStats.depth = programStats.time =
7226         programStats.score = programStats.got_only_move = 0;
7227         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7228
7229         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7230         strcat(bookMove, bookHit);
7231         HandleMachineMove(bookMove, &first);
7232   }
7233   return 1;
7234 }
7235
7236 void
7237 MarkByFEN(char *fen)
7238 {
7239         int r, f;
7240         if(!appData.markers || !appData.highlightDragging) return;
7241         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7242         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7243         while(*fen) {
7244             int s = 0;
7245             marker[r][f] = 0;
7246             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7247             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7248             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7249             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7250             if(*fen == 'T') marker[r][f++] = 0; else
7251             if(*fen == 'Y') marker[r][f++] = 1; else
7252             if(*fen == 'G') marker[r][f++] = 3; else
7253             if(*fen == 'B') marker[r][f++] = 4; else
7254             if(*fen == 'C') marker[r][f++] = 5; else
7255             if(*fen == 'M') marker[r][f++] = 6; else
7256             if(*fen == 'W') marker[r][f++] = 7; else
7257             if(*fen == 'D') marker[r][f++] = 8; else
7258             if(*fen == 'R') marker[r][f++] = 2; else {
7259                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7260               f += s; fen -= s>0;
7261             }
7262             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7263             if(r < 0) break;
7264             fen++;
7265         }
7266         DrawPosition(TRUE, NULL);
7267 }
7268
7269 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7270
7271 void
7272 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7273 {
7274     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7275     Markers *m = (Markers *) closure;
7276     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7277         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7278                          || kind == WhiteCapturesEnPassant
7279                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7280     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7281 }
7282
7283 static int hoverSavedValid;
7284
7285 void
7286 MarkTargetSquares (int clear)
7287 {
7288   int x, y, sum=0;
7289   if(clear) { // no reason to ever suppress clearing
7290     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7291     hoverSavedValid = 0;
7292     if(!sum) return; // nothing was cleared,no redraw needed
7293   } else {
7294     int capt = 0;
7295     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7296        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7297     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7298     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7299       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7300       if(capt)
7301       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7302     }
7303   }
7304   DrawPosition(FALSE, NULL);
7305 }
7306
7307 int
7308 Explode (Board board, int fromX, int fromY, int toX, int toY)
7309 {
7310     if(gameInfo.variant == VariantAtomic &&
7311        (board[toY][toX] != EmptySquare ||                     // capture?
7312         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7313                          board[fromY][fromX] == BlackPawn   )
7314       )) {
7315         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7316         return TRUE;
7317     }
7318     return FALSE;
7319 }
7320
7321 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7322
7323 int
7324 CanPromote (ChessSquare piece, int y)
7325 {
7326         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7327         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7328         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7329         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7330            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7331            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7332          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7333         return (piece == BlackPawn && y <= zone ||
7334                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7335                 piece == BlackLance && y <= zone ||
7336                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7337 }
7338
7339 void
7340 HoverEvent (int xPix, int yPix, int x, int y)
7341 {
7342         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7343         int r, f;
7344         if(!first.highlight) return;
7345         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7346         if(x == oldX && y == oldY) return; // only do something if we enter new square
7347         oldFromX = fromX; oldFromY = fromY;
7348         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7349           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7350             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7351           hoverSavedValid = 1;
7352         } else if(oldX != x || oldY != y) {
7353           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7354           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7355           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7356             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7357           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7358             char buf[MSG_SIZ];
7359             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7360             SendToProgram(buf, &first);
7361           }
7362           oldX = x; oldY = y;
7363 //        SetHighlights(fromX, fromY, x, y);
7364         }
7365 }
7366
7367 void ReportClick(char *action, int x, int y)
7368 {
7369         char buf[MSG_SIZ]; // Inform engine of what user does
7370         int r, f;
7371         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7372           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7373             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7374         if(!first.highlight || gameMode == EditPosition) return;
7375         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7376         SendToProgram(buf, &first);
7377 }
7378
7379 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7380
7381 void
7382 LeftClick (ClickType clickType, int xPix, int yPix)
7383 {
7384     int x, y;
7385     Boolean saveAnimate;
7386     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7387     char promoChoice = NULLCHAR;
7388     ChessSquare piece;
7389     static TimeMark lastClickTime, prevClickTime;
7390
7391     x = EventToSquare(xPix, BOARD_WIDTH);
7392     y = EventToSquare(yPix, BOARD_HEIGHT);
7393     if (!flipView && y >= 0) {
7394         y = BOARD_HEIGHT - 1 - y;
7395     }
7396     if (flipView && x >= 0) {
7397         x = BOARD_WIDTH - 1 - x;
7398     }
7399
7400     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7401         static int dummy;
7402         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7403         right = TRUE;
7404         return;
7405     }
7406
7407     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7408
7409     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7410
7411     if (clickType == Press) ErrorPopDown();
7412     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7413
7414     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7415         defaultPromoChoice = promoSweep;
7416         promoSweep = EmptySquare;   // terminate sweep
7417         promoDefaultAltered = TRUE;
7418         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7419     }
7420
7421     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7422         if(clickType == Release) return; // ignore upclick of click-click destination
7423         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7424         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7425         if(gameInfo.holdingsWidth &&
7426                 (WhiteOnMove(currentMove)
7427                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7428                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7429             // click in right holdings, for determining promotion piece
7430             ChessSquare p = boards[currentMove][y][x];
7431             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7432             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7433             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7434                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7435                 fromX = fromY = -1;
7436                 return;
7437             }
7438         }
7439         DrawPosition(FALSE, boards[currentMove]);
7440         return;
7441     }
7442
7443     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7444     if(clickType == Press
7445             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7446               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7447               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7448         return;
7449
7450     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7451         // could be static click on premove from-square: abort premove
7452         gotPremove = 0;
7453         ClearPremoveHighlights();
7454     }
7455
7456     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7457         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7458
7459     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7460         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7461                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7462         defaultPromoChoice = DefaultPromoChoice(side);
7463     }
7464
7465     autoQueen = appData.alwaysPromoteToQueen;
7466
7467     if (fromX == -1) {
7468       int originalY = y;
7469       gatingPiece = EmptySquare;
7470       if (clickType != Press) {
7471         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7472             DragPieceEnd(xPix, yPix); dragging = 0;
7473             DrawPosition(FALSE, NULL);
7474         }
7475         return;
7476       }
7477       doubleClick = FALSE;
7478       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7479         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7480       }
7481       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7482       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7483          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7484          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7485             /* First square */
7486             if (OKToStartUserMove(fromX, fromY)) {
7487                 second = 0;
7488                 ReportClick("lift", x, y);
7489                 MarkTargetSquares(0);
7490                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7491                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7492                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7493                     promoSweep = defaultPromoChoice;
7494                     selectFlag = 0; lastX = xPix; lastY = yPix;
7495                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7496                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7497                 }
7498                 if (appData.highlightDragging) {
7499                     SetHighlights(fromX, fromY, -1, -1);
7500                 } else {
7501                     ClearHighlights();
7502                 }
7503             } else fromX = fromY = -1;
7504             return;
7505         }
7506     }
7507 printf("to click %d,%d\n",x,y);
7508     /* fromX != -1 */
7509     if (clickType == Press && gameMode != EditPosition) {
7510         ChessSquare fromP;
7511         ChessSquare toP;
7512         int frc;
7513
7514         // ignore off-board to clicks
7515         if(y < 0 || x < 0) return;
7516
7517         /* Check if clicking again on the same color piece */
7518         fromP = boards[currentMove][fromY][fromX];
7519         toP = boards[currentMove][y][x];
7520         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7521         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7522             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7523            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7524              WhitePawn <= toP && toP <= WhiteKing &&
7525              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7526              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7527             (BlackPawn <= fromP && fromP <= BlackKing &&
7528              BlackPawn <= toP && toP <= BlackKing &&
7529              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7530              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7531             /* Clicked again on same color piece -- changed his mind */
7532             second = (x == fromX && y == fromY);
7533             killX = killY = -1;
7534             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7535                 second = FALSE; // first double-click rather than scond click
7536                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7537             }
7538             promoDefaultAltered = FALSE;
7539             MarkTargetSquares(1);
7540            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7541             if (appData.highlightDragging) {
7542                 SetHighlights(x, y, -1, -1);
7543             } else {
7544                 ClearHighlights();
7545             }
7546             if (OKToStartUserMove(x, y)) {
7547                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7548                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7549                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7550                  gatingPiece = boards[currentMove][fromY][fromX];
7551                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7552                 fromX = x;
7553                 fromY = y; dragging = 1;
7554                 ReportClick("lift", x, y);
7555                 MarkTargetSquares(0);
7556                 DragPieceBegin(xPix, yPix, FALSE);
7557                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7558                     promoSweep = defaultPromoChoice;
7559                     selectFlag = 0; lastX = xPix; lastY = yPix;
7560                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7561                 }
7562             }
7563            }
7564            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7565            second = FALSE;
7566         }
7567         // ignore clicks on holdings
7568         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7569     }
7570 printf("A type=%d\n",clickType);
7571
7572     if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7573         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7574         return;
7575     }
7576
7577     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7578         DragPieceEnd(xPix, yPix); dragging = 0;
7579         if(clearFlag) {
7580             // a deferred attempt to click-click move an empty square on top of a piece
7581             boards[currentMove][y][x] = EmptySquare;
7582             ClearHighlights();
7583             DrawPosition(FALSE, boards[currentMove]);
7584             fromX = fromY = -1; clearFlag = 0;
7585             return;
7586         }
7587         if (appData.animateDragging) {
7588             /* Undo animation damage if any */
7589             DrawPosition(FALSE, NULL);
7590         }
7591         if (second || sweepSelecting) {
7592             /* Second up/down in same square; just abort move */
7593             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7594             second = sweepSelecting = 0;
7595             fromX = fromY = -1;
7596             gatingPiece = EmptySquare;
7597             MarkTargetSquares(1);
7598             ClearHighlights();
7599             gotPremove = 0;
7600             ClearPremoveHighlights();
7601         } else {
7602             /* First upclick in same square; start click-click mode */
7603             SetHighlights(x, y, -1, -1);
7604         }
7605         return;
7606     }
7607
7608     clearFlag = 0;
7609 printf("B\n");
7610     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7611        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7612         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7613         DisplayMessage(_("only marked squares are legal"),"");
7614         DrawPosition(TRUE, NULL);
7615         return; // ignore to-click
7616     }
7617 printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
7618     /* we now have a different from- and (possibly off-board) to-square */
7619     /* Completed move */
7620     if(!sweepSelecting) {
7621         toX = x;
7622         toY = y;
7623     }
7624
7625     piece = boards[currentMove][fromY][fromX];
7626
7627     saveAnimate = appData.animate;
7628     if (clickType == Press) {
7629         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7630         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7631             // must be Edit Position mode with empty-square selected
7632             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7633             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7634             return;
7635         }
7636         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7637             return;
7638         }
7639         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7640             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7641         } else
7642         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7643         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7644           if(appData.sweepSelect) {
7645             promoSweep = defaultPromoChoice;
7646             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7647             selectFlag = 0; lastX = xPix; lastY = yPix;
7648             Sweep(0); // Pawn that is going to promote: preview promotion piece
7649             sweepSelecting = 1;
7650             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7651             MarkTargetSquares(1);
7652           }
7653           return; // promo popup appears on up-click
7654         }
7655         /* Finish clickclick move */
7656         if (appData.animate || appData.highlightLastMove) {
7657             SetHighlights(fromX, fromY, toX, toY);
7658         } else {
7659             ClearHighlights();
7660         }
7661     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7662         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7663         if (appData.animate || appData.highlightLastMove) {
7664             SetHighlights(fromX, fromY, toX, toY);
7665         } else {
7666             ClearHighlights();
7667         }
7668     } else {
7669 #if 0
7670 // [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
7671         /* Finish drag move */
7672         if (appData.highlightLastMove) {
7673             SetHighlights(fromX, fromY, toX, toY);
7674         } else {
7675             ClearHighlights();
7676         }
7677 #endif
7678         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7679         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7680           dragging *= 2;            // flag button-less dragging if we are dragging
7681           MarkTargetSquares(1);
7682           if(x == killX && y == killY) killX = killY = -1; else {
7683             killX = x; killY = y;     //remeber this square as intermediate
7684             ReportClick("put", x, y); // and inform engine
7685             ReportClick("lift", x, y);
7686             MarkTargetSquares(0);
7687             return;
7688           }
7689         }
7690         DragPieceEnd(xPix, yPix); dragging = 0;
7691         /* Don't animate move and drag both */
7692         appData.animate = FALSE;
7693     }
7694
7695     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7696     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7697         ChessSquare piece = boards[currentMove][fromY][fromX];
7698         if(gameMode == EditPosition && piece != EmptySquare &&
7699            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7700             int n;
7701
7702             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7703                 n = PieceToNumber(piece - (int)BlackPawn);
7704                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7705                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7706                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7707             } else
7708             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7709                 n = PieceToNumber(piece);
7710                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7711                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7712                 boards[currentMove][n][BOARD_WIDTH-2]++;
7713             }
7714             boards[currentMove][fromY][fromX] = EmptySquare;
7715         }
7716         ClearHighlights();
7717         fromX = fromY = -1;
7718         MarkTargetSquares(1);
7719         DrawPosition(TRUE, boards[currentMove]);
7720         return;
7721     }
7722
7723     // off-board moves should not be highlighted
7724     if(x < 0 || y < 0) ClearHighlights();
7725     else ReportClick("put", x, y);
7726
7727     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7728
7729     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7730
7731     if (legal[toY][toX] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7732         SetHighlights(fromX, fromY, toX, toY);
7733         MarkTargetSquares(1);
7734         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7735             // [HGM] super: promotion to captured piece selected from holdings
7736             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7737             promotionChoice = TRUE;
7738             // kludge follows to temporarily execute move on display, without promoting yet
7739             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7740             boards[currentMove][toY][toX] = p;
7741             DrawPosition(FALSE, boards[currentMove]);
7742             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7743             boards[currentMove][toY][toX] = q;
7744             DisplayMessage("Click in holdings to choose piece", "");
7745             return;
7746         }
7747         PromotionPopUp(promoChoice);
7748     } else {
7749         int oldMove = currentMove;
7750         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7751         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7752         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7753         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7754            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7755             DrawPosition(TRUE, boards[currentMove]);
7756         MarkTargetSquares(1);
7757         fromX = fromY = -1;
7758     }
7759     appData.animate = saveAnimate;
7760     if (appData.animate || appData.animateDragging) {
7761         /* Undo animation damage if needed */
7762         DrawPosition(FALSE, NULL);
7763     }
7764 }
7765
7766 int
7767 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7768 {   // front-end-free part taken out of PieceMenuPopup
7769     int whichMenu; int xSqr, ySqr;
7770
7771     if(seekGraphUp) { // [HGM] seekgraph
7772         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7773         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7774         return -2;
7775     }
7776
7777     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7778          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7779         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7780         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7781         if(action == Press)   {
7782             originalFlip = flipView;
7783             flipView = !flipView; // temporarily flip board to see game from partners perspective
7784             DrawPosition(TRUE, partnerBoard);
7785             DisplayMessage(partnerStatus, "");
7786             partnerUp = TRUE;
7787         } else if(action == Release) {
7788             flipView = originalFlip;
7789             DrawPosition(TRUE, boards[currentMove]);
7790             partnerUp = FALSE;
7791         }
7792         return -2;
7793     }
7794
7795     xSqr = EventToSquare(x, BOARD_WIDTH);
7796     ySqr = EventToSquare(y, BOARD_HEIGHT);
7797     if (action == Release) {
7798         if(pieceSweep != EmptySquare) {
7799             EditPositionMenuEvent(pieceSweep, toX, toY);
7800             pieceSweep = EmptySquare;
7801         } else UnLoadPV(); // [HGM] pv
7802     }
7803     if (action != Press) return -2; // return code to be ignored
7804     switch (gameMode) {
7805       case IcsExamining:
7806         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7807       case EditPosition:
7808         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7809         if (xSqr < 0 || ySqr < 0) return -1;
7810         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7811         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7812         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7813         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7814         NextPiece(0);
7815         return 2; // grab
7816       case IcsObserving:
7817         if(!appData.icsEngineAnalyze) return -1;
7818       case IcsPlayingWhite:
7819       case IcsPlayingBlack:
7820         if(!appData.zippyPlay) goto noZip;
7821       case AnalyzeMode:
7822       case AnalyzeFile:
7823       case MachinePlaysWhite:
7824       case MachinePlaysBlack:
7825       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7826         if (!appData.dropMenu) {
7827           LoadPV(x, y);
7828           return 2; // flag front-end to grab mouse events
7829         }
7830         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7831            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7832       case EditGame:
7833       noZip:
7834         if (xSqr < 0 || ySqr < 0) return -1;
7835         if (!appData.dropMenu || appData.testLegality &&
7836             gameInfo.variant != VariantBughouse &&
7837             gameInfo.variant != VariantCrazyhouse) return -1;
7838         whichMenu = 1; // drop menu
7839         break;
7840       default:
7841         return -1;
7842     }
7843
7844     if (((*fromX = xSqr) < 0) ||
7845         ((*fromY = ySqr) < 0)) {
7846         *fromX = *fromY = -1;
7847         return -1;
7848     }
7849     if (flipView)
7850       *fromX = BOARD_WIDTH - 1 - *fromX;
7851     else
7852       *fromY = BOARD_HEIGHT - 1 - *fromY;
7853
7854     return whichMenu;
7855 }
7856
7857 void
7858 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7859 {
7860 //    char * hint = lastHint;
7861     FrontEndProgramStats stats;
7862
7863     stats.which = cps == &first ? 0 : 1;
7864     stats.depth = cpstats->depth;
7865     stats.nodes = cpstats->nodes;
7866     stats.score = cpstats->score;
7867     stats.time = cpstats->time;
7868     stats.pv = cpstats->movelist;
7869     stats.hint = lastHint;
7870     stats.an_move_index = 0;
7871     stats.an_move_count = 0;
7872
7873     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7874         stats.hint = cpstats->move_name;
7875         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7876         stats.an_move_count = cpstats->nr_moves;
7877     }
7878
7879     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
7880
7881     SetProgramStats( &stats );
7882 }
7883
7884 void
7885 ClearEngineOutputPane (int which)
7886 {
7887     static FrontEndProgramStats dummyStats;
7888     dummyStats.which = which;
7889     dummyStats.pv = "#";
7890     SetProgramStats( &dummyStats );
7891 }
7892
7893 #define MAXPLAYERS 500
7894
7895 char *
7896 TourneyStandings (int display)
7897 {
7898     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7899     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7900     char result, *p, *names[MAXPLAYERS];
7901
7902     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7903         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7904     names[0] = p = strdup(appData.participants);
7905     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7906
7907     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7908
7909     while(result = appData.results[nr]) {
7910         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7911         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7912         wScore = bScore = 0;
7913         switch(result) {
7914           case '+': wScore = 2; break;
7915           case '-': bScore = 2; break;
7916           case '=': wScore = bScore = 1; break;
7917           case ' ':
7918           case '*': return strdup("busy"); // tourney not finished
7919         }
7920         score[w] += wScore;
7921         score[b] += bScore;
7922         games[w]++;
7923         games[b]++;
7924         nr++;
7925     }
7926     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7927     for(w=0; w<nPlayers; w++) {
7928         bScore = -1;
7929         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7930         ranking[w] = b; points[w] = bScore; score[b] = -2;
7931     }
7932     p = malloc(nPlayers*34+1);
7933     for(w=0; w<nPlayers && w<display; w++)
7934         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7935     free(names[0]);
7936     return p;
7937 }
7938
7939 void
7940 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7941 {       // count all piece types
7942         int p, f, r;
7943         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7944         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7945         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7946                 p = board[r][f];
7947                 pCnt[p]++;
7948                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7949                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7950                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7951                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7952                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7953                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7954         }
7955 }
7956
7957 int
7958 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7959 {
7960         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7961         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7962
7963         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7964         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7965         if(myPawns == 2 && nMine == 3) // KPP
7966             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7967         if(myPawns == 1 && nMine == 2) // KP
7968             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7969         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7970             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7971         if(myPawns) return FALSE;
7972         if(pCnt[WhiteRook+side])
7973             return pCnt[BlackRook-side] ||
7974                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7975                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7976                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7977         if(pCnt[WhiteCannon+side]) {
7978             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7979             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7980         }
7981         if(pCnt[WhiteKnight+side])
7982             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7983         return FALSE;
7984 }
7985
7986 int
7987 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7988 {
7989         VariantClass v = gameInfo.variant;
7990
7991         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7992         if(v == VariantShatranj) return TRUE; // always winnable through baring
7993         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7994         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7995
7996         if(v == VariantXiangqi) {
7997                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7998
7999                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8000                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8001                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8002                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8003                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8004                 if(stale) // we have at least one last-rank P plus perhaps C
8005                     return majors // KPKX
8006                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8007                 else // KCA*E*
8008                     return pCnt[WhiteFerz+side] // KCAK
8009                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8010                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8011                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8012
8013         } else if(v == VariantKnightmate) {
8014                 if(nMine == 1) return FALSE;
8015                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8016         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8017                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8018
8019                 if(nMine == 1) return FALSE; // bare King
8020                 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
8021                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8022                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8023                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8024                 if(pCnt[WhiteKnight+side])
8025                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8026                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8027                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8028                 if(nBishops)
8029                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8030                 if(pCnt[WhiteAlfil+side])
8031                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8032                 if(pCnt[WhiteWazir+side])
8033                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8034         }
8035
8036         return TRUE;
8037 }
8038
8039 int
8040 CompareWithRights (Board b1, Board b2)
8041 {
8042     int rights = 0;
8043     if(!CompareBoards(b1, b2)) return FALSE;
8044     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8045     /* compare castling rights */
8046     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8047            rights++; /* King lost rights, while rook still had them */
8048     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8049         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8050            rights++; /* but at least one rook lost them */
8051     }
8052     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8053            rights++;
8054     if( b1[CASTLING][5] != NoRights ) {
8055         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8056            rights++;
8057     }
8058     return rights == 0;
8059 }
8060
8061 int
8062 Adjudicate (ChessProgramState *cps)
8063 {       // [HGM] some adjudications useful with buggy engines
8064         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8065         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8066         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8067         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8068         int k, drop, count = 0; static int bare = 1;
8069         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8070         Boolean canAdjudicate = !appData.icsActive;
8071
8072         // most tests only when we understand the game, i.e. legality-checking on
8073             if( appData.testLegality )
8074             {   /* [HGM] Some more adjudications for obstinate engines */
8075                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8076                 static int moveCount = 6;
8077                 ChessMove result;
8078                 char *reason = NULL;
8079
8080                 /* Count what is on board. */
8081                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8082
8083                 /* Some material-based adjudications that have to be made before stalemate test */
8084                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8085                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8086                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8087                      if(canAdjudicate && appData.checkMates) {
8088                          if(engineOpponent)
8089                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8090                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8091                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8092                          return 1;
8093                      }
8094                 }
8095
8096                 /* Bare King in Shatranj (loses) or Losers (wins) */
8097                 if( nrW == 1 || nrB == 1) {
8098                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8099                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8100                      if(canAdjudicate && appData.checkMates) {
8101                          if(engineOpponent)
8102                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8103                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8104                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8105                          return 1;
8106                      }
8107                   } else
8108                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8109                   {    /* bare King */
8110                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8111                         if(canAdjudicate && appData.checkMates) {
8112                             /* but only adjudicate if adjudication enabled */
8113                             if(engineOpponent)
8114                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8115                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8116                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8117                             return 1;
8118                         }
8119                   }
8120                 } else bare = 1;
8121
8122
8123             // don't wait for engine to announce game end if we can judge ourselves
8124             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8125               case MT_CHECK:
8126                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8127                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8128                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8129                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8130                             checkCnt++;
8131                         if(checkCnt >= 2) {
8132                             reason = "Xboard adjudication: 3rd check";
8133                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8134                             break;
8135                         }
8136                     }
8137                 }
8138               case MT_NONE:
8139               default:
8140                 break;
8141               case MT_STEALMATE:
8142               case MT_STALEMATE:
8143               case MT_STAINMATE:
8144                 reason = "Xboard adjudication: Stalemate";
8145                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8146                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8147                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8148                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8149                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8150                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8151                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8152                                                                         EP_CHECKMATE : EP_WINS);
8153                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8154                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8155                 }
8156                 break;
8157               case MT_CHECKMATE:
8158                 reason = "Xboard adjudication: Checkmate";
8159                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8160                 if(gameInfo.variant == VariantShogi) {
8161                     if(forwardMostMove > backwardMostMove
8162                        && moveList[forwardMostMove-1][1] == '@'
8163                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8164                         reason = "XBoard adjudication: pawn-drop mate";
8165                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8166                     }
8167                 }
8168                 break;
8169             }
8170
8171                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8172                     case EP_STALEMATE:
8173                         result = GameIsDrawn; break;
8174                     case EP_CHECKMATE:
8175                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8176                     case EP_WINS:
8177                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8178                     default:
8179                         result = EndOfFile;
8180                 }
8181                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8182                     if(engineOpponent)
8183                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8184                     GameEnds( result, reason, GE_XBOARD );
8185                     return 1;
8186                 }
8187
8188                 /* Next absolutely insufficient mating material. */
8189                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8190                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8191                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8192
8193                      /* always flag draws, for judging claims */
8194                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8195
8196                      if(canAdjudicate && appData.materialDraws) {
8197                          /* but only adjudicate them if adjudication enabled */
8198                          if(engineOpponent) {
8199                            SendToProgram("force\n", engineOpponent); // suppress reply
8200                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8201                          }
8202                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8203                          return 1;
8204                      }
8205                 }
8206
8207                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8208                 if(gameInfo.variant == VariantXiangqi ?
8209                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8210                  : nrW + nrB == 4 &&
8211                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8212                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8213                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8214                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8215                    ) ) {
8216                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8217                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8218                           if(engineOpponent) {
8219                             SendToProgram("force\n", engineOpponent); // suppress reply
8220                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8221                           }
8222                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8223                           return 1;
8224                      }
8225                 } else moveCount = 6;
8226             }
8227
8228         // Repetition draws and 50-move rule can be applied independently of legality testing
8229
8230                 /* Check for rep-draws */
8231                 count = 0;
8232                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8233                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8234                 for(k = forwardMostMove-2;
8235                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8236                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8237                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8238                     k-=2)
8239                 {   int rights=0;
8240                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8241                         /* compare castling rights */
8242                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8243                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8244                                 rights++; /* King lost rights, while rook still had them */
8245                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8246                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8247                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8248                                    rights++; /* but at least one rook lost them */
8249                         }
8250                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8251                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8252                                 rights++;
8253                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8254                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8255                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8256                                    rights++;
8257                         }
8258                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8259                             && appData.drawRepeats > 1) {
8260                              /* adjudicate after user-specified nr of repeats */
8261                              int result = GameIsDrawn;
8262                              char *details = "XBoard adjudication: repetition draw";
8263                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8264                                 // [HGM] xiangqi: check for forbidden perpetuals
8265                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8266                                 for(m=forwardMostMove; m>k; m-=2) {
8267                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8268                                         ourPerpetual = 0; // the current mover did not always check
8269                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8270                                         hisPerpetual = 0; // the opponent did not always check
8271                                 }
8272                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8273                                                                         ourPerpetual, hisPerpetual);
8274                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8275                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8276                                     details = "Xboard adjudication: perpetual checking";
8277                                 } else
8278                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8279                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8280                                 } else
8281                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8282                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8283                                         result = BlackWins;
8284                                         details = "Xboard adjudication: repetition";
8285                                     }
8286                                 } else // it must be XQ
8287                                 // Now check for perpetual chases
8288                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8289                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8290                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8291                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8292                                         static char resdet[MSG_SIZ];
8293                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8294                                         details = resdet;
8295                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8296                                     } else
8297                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8298                                         break; // Abort repetition-checking loop.
8299                                 }
8300                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8301                              }
8302                              if(engineOpponent) {
8303                                SendToProgram("force\n", engineOpponent); // suppress reply
8304                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8305                              }
8306                              GameEnds( result, details, GE_XBOARD );
8307                              return 1;
8308                         }
8309                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8310                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8311                     }
8312                 }
8313
8314                 /* Now we test for 50-move draws. Determine ply count */
8315                 count = forwardMostMove;
8316                 /* look for last irreversble move */
8317                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8318                     count--;
8319                 /* if we hit starting position, add initial plies */
8320                 if( count == backwardMostMove )
8321                     count -= initialRulePlies;
8322                 count = forwardMostMove - count;
8323                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8324                         // adjust reversible move counter for checks in Xiangqi
8325                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8326                         if(i < backwardMostMove) i = backwardMostMove;
8327                         while(i <= forwardMostMove) {
8328                                 lastCheck = inCheck; // check evasion does not count
8329                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8330                                 if(inCheck || lastCheck) count--; // check does not count
8331                                 i++;
8332                         }
8333                 }
8334                 if( count >= 100)
8335                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8336                          /* this is used to judge if draw claims are legal */
8337                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8338                          if(engineOpponent) {
8339                            SendToProgram("force\n", engineOpponent); // suppress reply
8340                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8341                          }
8342                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8343                          return 1;
8344                 }
8345
8346                 /* if draw offer is pending, treat it as a draw claim
8347                  * when draw condition present, to allow engines a way to
8348                  * claim draws before making their move to avoid a race
8349                  * condition occurring after their move
8350                  */
8351                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8352                          char *p = NULL;
8353                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8354                              p = "Draw claim: 50-move rule";
8355                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8356                              p = "Draw claim: 3-fold repetition";
8357                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8358                              p = "Draw claim: insufficient mating material";
8359                          if( p != NULL && canAdjudicate) {
8360                              if(engineOpponent) {
8361                                SendToProgram("force\n", engineOpponent); // suppress reply
8362                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8363                              }
8364                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8365                              return 1;
8366                          }
8367                 }
8368
8369                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8370                     if(engineOpponent) {
8371                       SendToProgram("force\n", engineOpponent); // suppress reply
8372                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8373                     }
8374                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8375                     return 1;
8376                 }
8377         return 0;
8378 }
8379
8380 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8381 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8382 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8383
8384 static int
8385 BitbaseProbe ()
8386 {
8387     int pieces[10], squares[10], cnt=0, r, f, res;
8388     static int loaded;
8389     static PPROBE_EGBB probeBB;
8390     if(!appData.testLegality) return 10;
8391     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8392     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8393     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8394     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8395         ChessSquare piece = boards[forwardMostMove][r][f];
8396         int black = (piece >= BlackPawn);
8397         int type = piece - black*BlackPawn;
8398         if(piece == EmptySquare) continue;
8399         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8400         if(type == WhiteKing) type = WhiteQueen + 1;
8401         type = egbbCode[type];
8402         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8403         pieces[cnt] = type + black*6;
8404         if(++cnt > 5) return 11;
8405     }
8406     pieces[cnt] = squares[cnt] = 0;
8407     // probe EGBB
8408     if(loaded == 2) return 13; // loading failed before
8409     if(loaded == 0) {
8410         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8411         HMODULE lib;
8412         PLOAD_EGBB loadBB;
8413         loaded = 2; // prepare for failure
8414         if(!path) return 13; // no egbb installed
8415         strncpy(buf, path + 8, MSG_SIZ);
8416         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8417         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8418         lib = LoadLibrary(buf);
8419         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8420         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8421         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8422         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8423         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8424         loaded = 1; // success!
8425     }
8426     res = probeBB(forwardMostMove & 1, pieces, squares);
8427     return res > 0 ? 1 : res < 0 ? -1 : 0;
8428 }
8429
8430 char *
8431 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8432 {   // [HGM] book: this routine intercepts moves to simulate book replies
8433     char *bookHit = NULL;
8434
8435     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8436         char buf[MSG_SIZ];
8437         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8438         SendToProgram(buf, cps);
8439     }
8440     //first determine if the incoming move brings opponent into his book
8441     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8442         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8443     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8444     if(bookHit != NULL && !cps->bookSuspend) {
8445         // make sure opponent is not going to reply after receiving move to book position
8446         SendToProgram("force\n", cps);
8447         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8448     }
8449     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8450     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8451     // now arrange restart after book miss
8452     if(bookHit) {
8453         // after a book hit we never send 'go', and the code after the call to this routine
8454         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8455         char buf[MSG_SIZ], *move = bookHit;
8456         if(cps->useSAN) {
8457             int fromX, fromY, toX, toY;
8458             char promoChar;
8459             ChessMove moveType;
8460             move = buf + 30;
8461             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8462                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8463                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8464                                     PosFlags(forwardMostMove),
8465                                     fromY, fromX, toY, toX, promoChar, move);
8466             } else {
8467                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8468                 bookHit = NULL;
8469             }
8470         }
8471         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8472         SendToProgram(buf, cps);
8473         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8474     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8475         SendToProgram("go\n", cps);
8476         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8477     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8478         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8479             SendToProgram("go\n", cps);
8480         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8481     }
8482     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8483 }
8484
8485 int
8486 LoadError (char *errmess, ChessProgramState *cps)
8487 {   // unloads engine and switches back to -ncp mode if it was first
8488     if(cps->initDone) return FALSE;
8489     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8490     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8491     cps->pr = NoProc;
8492     if(cps == &first) {
8493         appData.noChessProgram = TRUE;
8494         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8495         gameMode = BeginningOfGame; ModeHighlight();
8496         SetNCPMode();
8497     }
8498     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8499     DisplayMessage("", ""); // erase waiting message
8500     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8501     return TRUE;
8502 }
8503
8504 char *savedMessage;
8505 ChessProgramState *savedState;
8506 void
8507 DeferredBookMove (void)
8508 {
8509         if(savedState->lastPing != savedState->lastPong)
8510                     ScheduleDelayedEvent(DeferredBookMove, 10);
8511         else
8512         HandleMachineMove(savedMessage, savedState);
8513 }
8514
8515 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8516 static ChessProgramState *stalledEngine;
8517 static char stashedInputMove[MSG_SIZ];
8518
8519 void
8520 HandleMachineMove (char *message, ChessProgramState *cps)
8521 {
8522     static char firstLeg[20];
8523     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8524     char realname[MSG_SIZ];
8525     int fromX, fromY, toX, toY;
8526     ChessMove moveType;
8527     char promoChar, roar;
8528     char *p, *pv=buf1;
8529     int machineWhite, oldError;
8530     char *bookHit;
8531
8532     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8533         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8534         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8535             DisplayError(_("Invalid pairing from pairing engine"), 0);
8536             return;
8537         }
8538         pairingReceived = 1;
8539         NextMatchGame();
8540         return; // Skim the pairing messages here.
8541     }
8542
8543     oldError = cps->userError; cps->userError = 0;
8544
8545 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8546     /*
8547      * Kludge to ignore BEL characters
8548      */
8549     while (*message == '\007') message++;
8550
8551     /*
8552      * [HGM] engine debug message: ignore lines starting with '#' character
8553      */
8554     if(cps->debug && *message == '#') return;
8555
8556     /*
8557      * Look for book output
8558      */
8559     if (cps == &first && bookRequested) {
8560         if (message[0] == '\t' || message[0] == ' ') {
8561             /* Part of the book output is here; append it */
8562             strcat(bookOutput, message);
8563             strcat(bookOutput, "  \n");
8564             return;
8565         } else if (bookOutput[0] != NULLCHAR) {
8566             /* All of book output has arrived; display it */
8567             char *p = bookOutput;
8568             while (*p != NULLCHAR) {
8569                 if (*p == '\t') *p = ' ';
8570                 p++;
8571             }
8572             DisplayInformation(bookOutput);
8573             bookRequested = FALSE;
8574             /* Fall through to parse the current output */
8575         }
8576     }
8577
8578     /*
8579      * Look for machine move.
8580      */
8581     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8582         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8583     {
8584         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8585             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8586             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8587             stalledEngine = cps;
8588             if(appData.ponderNextMove) { // bring opponent out of ponder
8589                 if(gameMode == TwoMachinesPlay) {
8590                     if(cps->other->pause)
8591                         PauseEngine(cps->other);
8592                     else
8593                         SendToProgram("easy\n", cps->other);
8594                 }
8595             }
8596             StopClocks();
8597             return;
8598         }
8599
8600         /* This method is only useful on engines that support ping */
8601         if (cps->lastPing != cps->lastPong) {
8602           if (gameMode == BeginningOfGame) {
8603             /* Extra move from before last new; ignore */
8604             if (appData.debugMode) {
8605                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8606             }
8607           } else {
8608             if (appData.debugMode) {
8609                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8610                         cps->which, gameMode);
8611             }
8612
8613             SendToProgram("undo\n", cps);
8614           }
8615           return;
8616         }
8617
8618         switch (gameMode) {
8619           case BeginningOfGame:
8620             /* Extra move from before last reset; ignore */
8621             if (appData.debugMode) {
8622                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8623             }
8624             return;
8625
8626           case EndOfGame:
8627           case IcsIdle:
8628           default:
8629             /* Extra move after we tried to stop.  The mode test is
8630                not a reliable way of detecting this problem, but it's
8631                the best we can do on engines that don't support ping.
8632             */
8633             if (appData.debugMode) {
8634                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8635                         cps->which, gameMode);
8636             }
8637             SendToProgram("undo\n", cps);
8638             return;
8639
8640           case MachinePlaysWhite:
8641           case IcsPlayingWhite:
8642             machineWhite = TRUE;
8643             break;
8644
8645           case MachinePlaysBlack:
8646           case IcsPlayingBlack:
8647             machineWhite = FALSE;
8648             break;
8649
8650           case TwoMachinesPlay:
8651             machineWhite = (cps->twoMachinesColor[0] == 'w');
8652             break;
8653         }
8654         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8655             if (appData.debugMode) {
8656                 fprintf(debugFP,
8657                         "Ignoring move out of turn by %s, gameMode %d"
8658                         ", forwardMost %d\n",
8659                         cps->which, gameMode, forwardMostMove);
8660             }
8661             return;
8662         }
8663
8664         if(cps->alphaRank) AlphaRank(machineMove, 4);
8665
8666         // [HGM] lion: (some very limited) support for Alien protocol
8667         killX = killY = -1;
8668         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8669             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8670             return;
8671         } else if(firstLeg[0]) { // there was a previous leg;
8672             // only support case where same piece makes two step
8673             char buf[20], *p = machineMove+1, *q = buf+1, f;
8674             safeStrCpy(buf, machineMove, 20);
8675             while(isdigit(*q)) q++; // find start of to-square
8676             safeStrCpy(machineMove, firstLeg, 20);
8677             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8678             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8679             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8680             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8681             firstLeg[0] = NULLCHAR;
8682         }
8683
8684         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8685                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8686             /* Machine move could not be parsed; ignore it. */
8687           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8688                     machineMove, _(cps->which));
8689             DisplayMoveError(buf1);
8690             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8691                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8692             if (gameMode == TwoMachinesPlay) {
8693               GameEnds(machineWhite ? BlackWins : WhiteWins,
8694                        buf1, GE_XBOARD);
8695             }
8696             return;
8697         }
8698
8699         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8700         /* So we have to redo legality test with true e.p. status here,  */
8701         /* to make sure an illegal e.p. capture does not slip through,   */
8702         /* to cause a forfeit on a justified illegal-move complaint      */
8703         /* of the opponent.                                              */
8704         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8705            ChessMove moveType;
8706            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8707                              fromY, fromX, toY, toX, promoChar);
8708             if(moveType == IllegalMove) {
8709               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8710                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8711                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8712                            buf1, GE_XBOARD);
8713                 return;
8714            } else if(!appData.fischerCastling)
8715            /* [HGM] Kludge to handle engines that send FRC-style castling
8716               when they shouldn't (like TSCP-Gothic) */
8717            switch(moveType) {
8718              case WhiteASideCastleFR:
8719              case BlackASideCastleFR:
8720                toX+=2;
8721                currentMoveString[2]++;
8722                break;
8723              case WhiteHSideCastleFR:
8724              case BlackHSideCastleFR:
8725                toX--;
8726                currentMoveString[2]--;
8727                break;
8728              default: ; // nothing to do, but suppresses warning of pedantic compilers
8729            }
8730         }
8731         hintRequested = FALSE;
8732         lastHint[0] = NULLCHAR;
8733         bookRequested = FALSE;
8734         /* Program may be pondering now */
8735         cps->maybeThinking = TRUE;
8736         if (cps->sendTime == 2) cps->sendTime = 1;
8737         if (cps->offeredDraw) cps->offeredDraw--;
8738
8739         /* [AS] Save move info*/
8740         pvInfoList[ forwardMostMove ].score = programStats.score;
8741         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8742         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8743
8744         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8745
8746         /* Test suites abort the 'game' after one move */
8747         if(*appData.finger) {
8748            static FILE *f;
8749            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8750            if(!f) f = fopen(appData.finger, "w");
8751            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8752            else { DisplayFatalError("Bad output file", errno, 0); return; }
8753            free(fen);
8754            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8755         }
8756
8757         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8758         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8759             int count = 0;
8760
8761             while( count < adjudicateLossPlies ) {
8762                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8763
8764                 if( count & 1 ) {
8765                     score = -score; /* Flip score for winning side */
8766                 }
8767
8768                 if( score > appData.adjudicateLossThreshold ) {
8769                     break;
8770                 }
8771
8772                 count++;
8773             }
8774
8775             if( count >= adjudicateLossPlies ) {
8776                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8777
8778                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8779                     "Xboard adjudication",
8780                     GE_XBOARD );
8781
8782                 return;
8783             }
8784         }
8785
8786         if(Adjudicate(cps)) {
8787             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8788             return; // [HGM] adjudicate: for all automatic game ends
8789         }
8790
8791 #if ZIPPY
8792         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8793             first.initDone) {
8794           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8795                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8796                 SendToICS("draw ");
8797                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8798           }
8799           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8800           ics_user_moved = 1;
8801           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8802                 char buf[3*MSG_SIZ];
8803
8804                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8805                         programStats.score / 100.,
8806                         programStats.depth,
8807                         programStats.time / 100.,
8808                         (unsigned int)programStats.nodes,
8809                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8810                         programStats.movelist);
8811                 SendToICS(buf);
8812           }
8813         }
8814 #endif
8815
8816         /* [AS] Clear stats for next move */
8817         ClearProgramStats();
8818         thinkOutput[0] = NULLCHAR;
8819         hiddenThinkOutputState = 0;
8820
8821         bookHit = NULL;
8822         if (gameMode == TwoMachinesPlay) {
8823             /* [HGM] relaying draw offers moved to after reception of move */
8824             /* and interpreting offer as claim if it brings draw condition */
8825             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8826                 SendToProgram("draw\n", cps->other);
8827             }
8828             if (cps->other->sendTime) {
8829                 SendTimeRemaining(cps->other,
8830                                   cps->other->twoMachinesColor[0] == 'w');
8831             }
8832             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8833             if (firstMove && !bookHit) {
8834                 firstMove = FALSE;
8835                 if (cps->other->useColors) {
8836                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8837                 }
8838                 SendToProgram("go\n", cps->other);
8839             }
8840             cps->other->maybeThinking = TRUE;
8841         }
8842
8843         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8844
8845         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8846
8847         if (!pausing && appData.ringBellAfterMoves) {
8848             if(!roar) RingBell();
8849         }
8850
8851         /*
8852          * Reenable menu items that were disabled while
8853          * machine was thinking
8854          */
8855         if (gameMode != TwoMachinesPlay)
8856             SetUserThinkingEnables();
8857
8858         // [HGM] book: after book hit opponent has received move and is now in force mode
8859         // force the book reply into it, and then fake that it outputted this move by jumping
8860         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8861         if(bookHit) {
8862                 static char bookMove[MSG_SIZ]; // a bit generous?
8863
8864                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8865                 strcat(bookMove, bookHit);
8866                 message = bookMove;
8867                 cps = cps->other;
8868                 programStats.nodes = programStats.depth = programStats.time =
8869                 programStats.score = programStats.got_only_move = 0;
8870                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8871
8872                 if(cps->lastPing != cps->lastPong) {
8873                     savedMessage = message; // args for deferred call
8874                     savedState = cps;
8875                     ScheduleDelayedEvent(DeferredBookMove, 10);
8876                     return;
8877                 }
8878                 goto FakeBookMove;
8879         }
8880
8881         return;
8882     }
8883
8884     /* Set special modes for chess engines.  Later something general
8885      *  could be added here; for now there is just one kludge feature,
8886      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8887      *  when "xboard" is given as an interactive command.
8888      */
8889     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8890         cps->useSigint = FALSE;
8891         cps->useSigterm = FALSE;
8892     }
8893     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8894       ParseFeatures(message+8, cps);
8895       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8896     }
8897
8898     if (!strncmp(message, "setup ", 6) && 
8899         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8900           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8901                                         ) { // [HGM] allow first engine to define opening position
8902       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8903       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8904       *buf = NULLCHAR;
8905       if(sscanf(message, "setup (%s", buf) == 1) {
8906         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8907         ASSIGN(appData.pieceToCharTable, buf);
8908       }
8909       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8910       if(dummy >= 3) {
8911         while(message[s] && message[s++] != ' ');
8912         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8913            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8914             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8915             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8916           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8917           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8918           startedFromSetupPosition = FALSE;
8919         }
8920       }
8921       if(startedFromSetupPosition) return;
8922       ParseFEN(boards[0], &dummy, message+s, FALSE);
8923       DrawPosition(TRUE, boards[0]);
8924       CopyBoard(initialPosition, boards[0]);
8925       startedFromSetupPosition = TRUE;
8926       return;
8927     }
8928     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8929       ChessSquare piece = WhitePawn;
8930       char *p=buf2;
8931       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8932       piece += CharToPiece(*p) - WhitePawn;
8933       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8934       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8935       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8936       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8937       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8938       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8939                                                && gameInfo.variant != VariantGreat
8940                                                && gameInfo.variant != VariantFairy    ) return;
8941       if(piece < EmptySquare) {
8942         pieceDefs = TRUE;
8943         ASSIGN(pieceDesc[piece], buf1);
8944         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8945       }
8946       return;
8947     }
8948     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8949      * want this, I was asked to put it in, and obliged.
8950      */
8951     if (!strncmp(message, "setboard ", 9)) {
8952         Board initial_position;
8953
8954         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8955
8956         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8957             DisplayError(_("Bad FEN received from engine"), 0);
8958             return ;
8959         } else {
8960            Reset(TRUE, FALSE);
8961            CopyBoard(boards[0], initial_position);
8962            initialRulePlies = FENrulePlies;
8963            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8964            else gameMode = MachinePlaysBlack;
8965            DrawPosition(FALSE, boards[currentMove]);
8966         }
8967         return;
8968     }
8969
8970     /*
8971      * Look for communication commands
8972      */
8973     if (!strncmp(message, "telluser ", 9)) {
8974         if(message[9] == '\\' && message[10] == '\\')
8975             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8976         PlayTellSound();
8977         DisplayNote(message + 9);
8978         return;
8979     }
8980     if (!strncmp(message, "tellusererror ", 14)) {
8981         cps->userError = 1;
8982         if(message[14] == '\\' && message[15] == '\\')
8983             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8984         PlayTellSound();
8985         DisplayError(message + 14, 0);
8986         return;
8987     }
8988     if (!strncmp(message, "tellopponent ", 13)) {
8989       if (appData.icsActive) {
8990         if (loggedOn) {
8991           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8992           SendToICS(buf1);
8993         }
8994       } else {
8995         DisplayNote(message + 13);
8996       }
8997       return;
8998     }
8999     if (!strncmp(message, "tellothers ", 11)) {
9000       if (appData.icsActive) {
9001         if (loggedOn) {
9002           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9003           SendToICS(buf1);
9004         }
9005       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9006       return;
9007     }
9008     if (!strncmp(message, "tellall ", 8)) {
9009       if (appData.icsActive) {
9010         if (loggedOn) {
9011           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9012           SendToICS(buf1);
9013         }
9014       } else {
9015         DisplayNote(message + 8);
9016       }
9017       return;
9018     }
9019     if (strncmp(message, "warning", 7) == 0) {
9020         /* Undocumented feature, use tellusererror in new code */
9021         DisplayError(message, 0);
9022         return;
9023     }
9024     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9025         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9026         strcat(realname, " query");
9027         AskQuestion(realname, buf2, buf1, cps->pr);
9028         return;
9029     }
9030     /* Commands from the engine directly to ICS.  We don't allow these to be
9031      *  sent until we are logged on. Crafty kibitzes have been known to
9032      *  interfere with the login process.
9033      */
9034     if (loggedOn) {
9035         if (!strncmp(message, "tellics ", 8)) {
9036             SendToICS(message + 8);
9037             SendToICS("\n");
9038             return;
9039         }
9040         if (!strncmp(message, "tellicsnoalias ", 15)) {
9041             SendToICS(ics_prefix);
9042             SendToICS(message + 15);
9043             SendToICS("\n");
9044             return;
9045         }
9046         /* The following are for backward compatibility only */
9047         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9048             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9049             SendToICS(ics_prefix);
9050             SendToICS(message);
9051             SendToICS("\n");
9052             return;
9053         }
9054     }
9055     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9056         if(initPing == cps->lastPong) {
9057             if(gameInfo.variant == VariantUnknown) {
9058                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9059                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9060                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9061             }
9062             initPing = -1;
9063         }
9064         return;
9065     }
9066     if(!strncmp(message, "highlight ", 10)) {
9067         if(appData.testLegality && appData.markers) return;
9068         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9069         return;
9070     }
9071     if(!strncmp(message, "click ", 6)) {
9072         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9073         if(appData.testLegality || !appData.oneClick) return;
9074         sscanf(message+6, "%c%d%c", &f, &y, &c);
9075         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9076         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9077         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9078         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9079         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9080         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9081             LeftClick(Release, lastLeftX, lastLeftY);
9082         controlKey  = (c == ',');
9083         LeftClick(Press, x, y);
9084         LeftClick(Release, x, y);
9085         first.highlight = f;
9086         return;
9087     }
9088     /*
9089      * If the move is illegal, cancel it and redraw the board.
9090      * Also deal with other error cases.  Matching is rather loose
9091      * here to accommodate engines written before the spec.
9092      */
9093     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9094         strncmp(message, "Error", 5) == 0) {
9095         if (StrStr(message, "name") ||
9096             StrStr(message, "rating") || StrStr(message, "?") ||
9097             StrStr(message, "result") || StrStr(message, "board") ||
9098             StrStr(message, "bk") || StrStr(message, "computer") ||
9099             StrStr(message, "variant") || StrStr(message, "hint") ||
9100             StrStr(message, "random") || StrStr(message, "depth") ||
9101             StrStr(message, "accepted")) {
9102             return;
9103         }
9104         if (StrStr(message, "protover")) {
9105           /* Program is responding to input, so it's apparently done
9106              initializing, and this error message indicates it is
9107              protocol version 1.  So we don't need to wait any longer
9108              for it to initialize and send feature commands. */
9109           FeatureDone(cps, 1);
9110           cps->protocolVersion = 1;
9111           return;
9112         }
9113         cps->maybeThinking = FALSE;
9114
9115         if (StrStr(message, "draw")) {
9116             /* Program doesn't have "draw" command */
9117             cps->sendDrawOffers = 0;
9118             return;
9119         }
9120         if (cps->sendTime != 1 &&
9121             (StrStr(message, "time") || StrStr(message, "otim"))) {
9122           /* Program apparently doesn't have "time" or "otim" command */
9123           cps->sendTime = 0;
9124           return;
9125         }
9126         if (StrStr(message, "analyze")) {
9127             cps->analysisSupport = FALSE;
9128             cps->analyzing = FALSE;
9129 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9130             EditGameEvent(); // [HGM] try to preserve loaded game
9131             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9132             DisplayError(buf2, 0);
9133             return;
9134         }
9135         if (StrStr(message, "(no matching move)st")) {
9136           /* Special kludge for GNU Chess 4 only */
9137           cps->stKludge = TRUE;
9138           SendTimeControl(cps, movesPerSession, timeControl,
9139                           timeIncrement, appData.searchDepth,
9140                           searchTime);
9141           return;
9142         }
9143         if (StrStr(message, "(no matching move)sd")) {
9144           /* Special kludge for GNU Chess 4 only */
9145           cps->sdKludge = TRUE;
9146           SendTimeControl(cps, movesPerSession, timeControl,
9147                           timeIncrement, appData.searchDepth,
9148                           searchTime);
9149           return;
9150         }
9151         if (!StrStr(message, "llegal")) {
9152             return;
9153         }
9154         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9155             gameMode == IcsIdle) return;
9156         if (forwardMostMove <= backwardMostMove) return;
9157         if (pausing) PauseEvent();
9158       if(appData.forceIllegal) {
9159             // [HGM] illegal: machine refused move; force position after move into it
9160           SendToProgram("force\n", cps);
9161           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9162                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9163                 // when black is to move, while there might be nothing on a2 or black
9164                 // might already have the move. So send the board as if white has the move.
9165                 // But first we must change the stm of the engine, as it refused the last move
9166                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9167                 if(WhiteOnMove(forwardMostMove)) {
9168                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9169                     SendBoard(cps, forwardMostMove); // kludgeless board
9170                 } else {
9171                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9172                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9173                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9174                 }
9175           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9176             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9177                  gameMode == TwoMachinesPlay)
9178               SendToProgram("go\n", cps);
9179             return;
9180       } else
9181         if (gameMode == PlayFromGameFile) {
9182             /* Stop reading this game file */
9183             gameMode = EditGame;
9184             ModeHighlight();
9185         }
9186         /* [HGM] illegal-move claim should forfeit game when Xboard */
9187         /* only passes fully legal moves                            */
9188         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9189             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9190                                 "False illegal-move claim", GE_XBOARD );
9191             return; // do not take back move we tested as valid
9192         }
9193         currentMove = forwardMostMove-1;
9194         DisplayMove(currentMove-1); /* before DisplayMoveError */
9195         SwitchClocks(forwardMostMove-1); // [HGM] race
9196         DisplayBothClocks();
9197         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9198                 parseList[currentMove], _(cps->which));
9199         DisplayMoveError(buf1);
9200         DrawPosition(FALSE, boards[currentMove]);
9201
9202         SetUserThinkingEnables();
9203         return;
9204     }
9205     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9206         /* Program has a broken "time" command that
9207            outputs a string not ending in newline.
9208            Don't use it. */
9209         cps->sendTime = 0;
9210     }
9211     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9212         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9213             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9214     }
9215
9216     /*
9217      * If chess program startup fails, exit with an error message.
9218      * Attempts to recover here are futile. [HGM] Well, we try anyway
9219      */
9220     if ((StrStr(message, "unknown host") != NULL)
9221         || (StrStr(message, "No remote directory") != NULL)
9222         || (StrStr(message, "not found") != NULL)
9223         || (StrStr(message, "No such file") != NULL)
9224         || (StrStr(message, "can't alloc") != NULL)
9225         || (StrStr(message, "Permission denied") != NULL)) {
9226
9227         cps->maybeThinking = FALSE;
9228         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9229                 _(cps->which), cps->program, cps->host, message);
9230         RemoveInputSource(cps->isr);
9231         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9232             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9233             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9234         }
9235         return;
9236     }
9237
9238     /*
9239      * Look for hint output
9240      */
9241     if (sscanf(message, "Hint: %s", buf1) == 1) {
9242         if (cps == &first && hintRequested) {
9243             hintRequested = FALSE;
9244             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9245                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9246                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9247                                     PosFlags(forwardMostMove),
9248                                     fromY, fromX, toY, toX, promoChar, buf1);
9249                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9250                 DisplayInformation(buf2);
9251             } else {
9252                 /* Hint move could not be parsed!? */
9253               snprintf(buf2, sizeof(buf2),
9254                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9255                         buf1, _(cps->which));
9256                 DisplayError(buf2, 0);
9257             }
9258         } else {
9259           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9260         }
9261         return;
9262     }
9263
9264     /*
9265      * Ignore other messages if game is not in progress
9266      */
9267     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9268         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9269
9270     /*
9271      * look for win, lose, draw, or draw offer
9272      */
9273     if (strncmp(message, "1-0", 3) == 0) {
9274         char *p, *q, *r = "";
9275         p = strchr(message, '{');
9276         if (p) {
9277             q = strchr(p, '}');
9278             if (q) {
9279                 *q = NULLCHAR;
9280                 r = p + 1;
9281             }
9282         }
9283         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9284         return;
9285     } else if (strncmp(message, "0-1", 3) == 0) {
9286         char *p, *q, *r = "";
9287         p = strchr(message, '{');
9288         if (p) {
9289             q = strchr(p, '}');
9290             if (q) {
9291                 *q = NULLCHAR;
9292                 r = p + 1;
9293             }
9294         }
9295         /* Kludge for Arasan 4.1 bug */
9296         if (strcmp(r, "Black resigns") == 0) {
9297             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9298             return;
9299         }
9300         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9301         return;
9302     } else if (strncmp(message, "1/2", 3) == 0) {
9303         char *p, *q, *r = "";
9304         p = strchr(message, '{');
9305         if (p) {
9306             q = strchr(p, '}');
9307             if (q) {
9308                 *q = NULLCHAR;
9309                 r = p + 1;
9310             }
9311         }
9312
9313         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9314         return;
9315
9316     } else if (strncmp(message, "White resign", 12) == 0) {
9317         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9318         return;
9319     } else if (strncmp(message, "Black resign", 12) == 0) {
9320         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9321         return;
9322     } else if (strncmp(message, "White matches", 13) == 0 ||
9323                strncmp(message, "Black matches", 13) == 0   ) {
9324         /* [HGM] ignore GNUShogi noises */
9325         return;
9326     } else if (strncmp(message, "White", 5) == 0 &&
9327                message[5] != '(' &&
9328                StrStr(message, "Black") == NULL) {
9329         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9330         return;
9331     } else if (strncmp(message, "Black", 5) == 0 &&
9332                message[5] != '(') {
9333         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9334         return;
9335     } else if (strcmp(message, "resign") == 0 ||
9336                strcmp(message, "computer resigns") == 0) {
9337         switch (gameMode) {
9338           case MachinePlaysBlack:
9339           case IcsPlayingBlack:
9340             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9341             break;
9342           case MachinePlaysWhite:
9343           case IcsPlayingWhite:
9344             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9345             break;
9346           case TwoMachinesPlay:
9347             if (cps->twoMachinesColor[0] == 'w')
9348               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9349             else
9350               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9351             break;
9352           default:
9353             /* can't happen */
9354             break;
9355         }
9356         return;
9357     } else if (strncmp(message, "opponent mates", 14) == 0) {
9358         switch (gameMode) {
9359           case MachinePlaysBlack:
9360           case IcsPlayingBlack:
9361             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9362             break;
9363           case MachinePlaysWhite:
9364           case IcsPlayingWhite:
9365             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9366             break;
9367           case TwoMachinesPlay:
9368             if (cps->twoMachinesColor[0] == 'w')
9369               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9370             else
9371               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9372             break;
9373           default:
9374             /* can't happen */
9375             break;
9376         }
9377         return;
9378     } else if (strncmp(message, "computer mates", 14) == 0) {
9379         switch (gameMode) {
9380           case MachinePlaysBlack:
9381           case IcsPlayingBlack:
9382             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9383             break;
9384           case MachinePlaysWhite:
9385           case IcsPlayingWhite:
9386             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9387             break;
9388           case TwoMachinesPlay:
9389             if (cps->twoMachinesColor[0] == 'w')
9390               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9391             else
9392               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9393             break;
9394           default:
9395             /* can't happen */
9396             break;
9397         }
9398         return;
9399     } else if (strncmp(message, "checkmate", 9) == 0) {
9400         if (WhiteOnMove(forwardMostMove)) {
9401             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9402         } else {
9403             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9404         }
9405         return;
9406     } else if (strstr(message, "Draw") != NULL ||
9407                strstr(message, "game is a draw") != NULL) {
9408         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9409         return;
9410     } else if (strstr(message, "offer") != NULL &&
9411                strstr(message, "draw") != NULL) {
9412 #if ZIPPY
9413         if (appData.zippyPlay && first.initDone) {
9414             /* Relay offer to ICS */
9415             SendToICS(ics_prefix);
9416             SendToICS("draw\n");
9417         }
9418 #endif
9419         cps->offeredDraw = 2; /* valid until this engine moves twice */
9420         if (gameMode == TwoMachinesPlay) {
9421             if (cps->other->offeredDraw) {
9422                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9423             /* [HGM] in two-machine mode we delay relaying draw offer      */
9424             /* until after we also have move, to see if it is really claim */
9425             }
9426         } else if (gameMode == MachinePlaysWhite ||
9427                    gameMode == MachinePlaysBlack) {
9428           if (userOfferedDraw) {
9429             DisplayInformation(_("Machine accepts your draw offer"));
9430             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9431           } else {
9432             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9433           }
9434         }
9435     }
9436
9437
9438     /*
9439      * Look for thinking output
9440      */
9441     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9442           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9443                                 ) {
9444         int plylev, mvleft, mvtot, curscore, time;
9445         char mvname[MOVE_LEN];
9446         u64 nodes; // [DM]
9447         char plyext;
9448         int ignore = FALSE;
9449         int prefixHint = FALSE;
9450         mvname[0] = NULLCHAR;
9451
9452         switch (gameMode) {
9453           case MachinePlaysBlack:
9454           case IcsPlayingBlack:
9455             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9456             break;
9457           case MachinePlaysWhite:
9458           case IcsPlayingWhite:
9459             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9460             break;
9461           case AnalyzeMode:
9462           case AnalyzeFile:
9463             break;
9464           case IcsObserving: /* [DM] icsEngineAnalyze */
9465             if (!appData.icsEngineAnalyze) ignore = TRUE;
9466             break;
9467           case TwoMachinesPlay:
9468             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9469                 ignore = TRUE;
9470             }
9471             break;
9472           default:
9473             ignore = TRUE;
9474             break;
9475         }
9476
9477         if (!ignore) {
9478             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9479             buf1[0] = NULLCHAR;
9480             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9481                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9482
9483                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9484                     nodes += u64Const(0x100000000);
9485
9486                 if (plyext != ' ' && plyext != '\t') {
9487                     time *= 100;
9488                 }
9489
9490                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9491                 if( cps->scoreIsAbsolute &&
9492                     ( gameMode == MachinePlaysBlack ||
9493                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9494                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9495                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9496                      !WhiteOnMove(currentMove)
9497                     ) )
9498                 {
9499                     curscore = -curscore;
9500                 }
9501
9502                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9503
9504                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9505                         char buf[MSG_SIZ];
9506                         FILE *f;
9507                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9508                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9509                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9510                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9511                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9512                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9513                                 fclose(f);
9514                         }
9515                         else
9516                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9517                           DisplayError(_("failed writing PV"), 0);
9518                 }
9519
9520                 tempStats.depth = plylev;
9521                 tempStats.nodes = nodes;
9522                 tempStats.time = time;
9523                 tempStats.score = curscore;
9524                 tempStats.got_only_move = 0;
9525
9526                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9527                         int ticklen;
9528
9529                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9530                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9531                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9532                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9533                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9534                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9535                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9536                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9537                 }
9538
9539                 /* Buffer overflow protection */
9540                 if (pv[0] != NULLCHAR) {
9541                     if (strlen(pv) >= sizeof(tempStats.movelist)
9542                         && appData.debugMode) {
9543                         fprintf(debugFP,
9544                                 "PV is too long; using the first %u bytes.\n",
9545                                 (unsigned) sizeof(tempStats.movelist) - 1);
9546                     }
9547
9548                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9549                 } else {
9550                     sprintf(tempStats.movelist, " no PV\n");
9551                 }
9552
9553                 if (tempStats.seen_stat) {
9554                     tempStats.ok_to_send = 1;
9555                 }
9556
9557                 if (strchr(tempStats.movelist, '(') != NULL) {
9558                     tempStats.line_is_book = 1;
9559                     tempStats.nr_moves = 0;
9560                     tempStats.moves_left = 0;
9561                 } else {
9562                     tempStats.line_is_book = 0;
9563                 }
9564
9565                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9566                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9567
9568                 SendProgramStatsToFrontend( cps, &tempStats );
9569
9570                 /*
9571                     [AS] Protect the thinkOutput buffer from overflow... this
9572                     is only useful if buf1 hasn't overflowed first!
9573                 */
9574                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9575                          plylev,
9576                          (gameMode == TwoMachinesPlay ?
9577                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9578                          ((double) curscore) / 100.0,
9579                          prefixHint ? lastHint : "",
9580                          prefixHint ? " " : "" );
9581
9582                 if( buf1[0] != NULLCHAR ) {
9583                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9584
9585                     if( strlen(pv) > max_len ) {
9586                         if( appData.debugMode) {
9587                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9588                         }
9589                         pv[max_len+1] = '\0';
9590                     }
9591
9592                     strcat( thinkOutput, pv);
9593                 }
9594
9595                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9596                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9597                     DisplayMove(currentMove - 1);
9598                 }
9599                 return;
9600
9601             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9602                 /* crafty (9.25+) says "(only move) <move>"
9603                  * if there is only 1 legal move
9604                  */
9605                 sscanf(p, "(only move) %s", buf1);
9606                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9607                 sprintf(programStats.movelist, "%s (only move)", buf1);
9608                 programStats.depth = 1;
9609                 programStats.nr_moves = 1;
9610                 programStats.moves_left = 1;
9611                 programStats.nodes = 1;
9612                 programStats.time = 1;
9613                 programStats.got_only_move = 1;
9614
9615                 /* Not really, but we also use this member to
9616                    mean "line isn't going to change" (Crafty
9617                    isn't searching, so stats won't change) */
9618                 programStats.line_is_book = 1;
9619
9620                 SendProgramStatsToFrontend( cps, &programStats );
9621
9622                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9623                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9624                     DisplayMove(currentMove - 1);
9625                 }
9626                 return;
9627             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9628                               &time, &nodes, &plylev, &mvleft,
9629                               &mvtot, mvname) >= 5) {
9630                 /* The stat01: line is from Crafty (9.29+) in response
9631                    to the "." command */
9632                 programStats.seen_stat = 1;
9633                 cps->maybeThinking = TRUE;
9634
9635                 if (programStats.got_only_move || !appData.periodicUpdates)
9636                   return;
9637
9638                 programStats.depth = plylev;
9639                 programStats.time = time;
9640                 programStats.nodes = nodes;
9641                 programStats.moves_left = mvleft;
9642                 programStats.nr_moves = mvtot;
9643                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9644                 programStats.ok_to_send = 1;
9645                 programStats.movelist[0] = '\0';
9646
9647                 SendProgramStatsToFrontend( cps, &programStats );
9648
9649                 return;
9650
9651             } else if (strncmp(message,"++",2) == 0) {
9652                 /* Crafty 9.29+ outputs this */
9653                 programStats.got_fail = 2;
9654                 return;
9655
9656             } else if (strncmp(message,"--",2) == 0) {
9657                 /* Crafty 9.29+ outputs this */
9658                 programStats.got_fail = 1;
9659                 return;
9660
9661             } else if (thinkOutput[0] != NULLCHAR &&
9662                        strncmp(message, "    ", 4) == 0) {
9663                 unsigned message_len;
9664
9665                 p = message;
9666                 while (*p && *p == ' ') p++;
9667
9668                 message_len = strlen( p );
9669
9670                 /* [AS] Avoid buffer overflow */
9671                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9672                     strcat(thinkOutput, " ");
9673                     strcat(thinkOutput, p);
9674                 }
9675
9676                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9677                     strcat(programStats.movelist, " ");
9678                     strcat(programStats.movelist, p);
9679                 }
9680
9681                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9682                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9683                     DisplayMove(currentMove - 1);
9684                 }
9685                 return;
9686             }
9687         }
9688         else {
9689             buf1[0] = NULLCHAR;
9690
9691             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9692                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9693             {
9694                 ChessProgramStats cpstats;
9695
9696                 if (plyext != ' ' && plyext != '\t') {
9697                     time *= 100;
9698                 }
9699
9700                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9701                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9702                     curscore = -curscore;
9703                 }
9704
9705                 cpstats.depth = plylev;
9706                 cpstats.nodes = nodes;
9707                 cpstats.time = time;
9708                 cpstats.score = curscore;
9709                 cpstats.got_only_move = 0;
9710                 cpstats.movelist[0] = '\0';
9711
9712                 if (buf1[0] != NULLCHAR) {
9713                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9714                 }
9715
9716                 cpstats.ok_to_send = 0;
9717                 cpstats.line_is_book = 0;
9718                 cpstats.nr_moves = 0;
9719                 cpstats.moves_left = 0;
9720
9721                 SendProgramStatsToFrontend( cps, &cpstats );
9722             }
9723         }
9724     }
9725 }
9726
9727
9728 /* Parse a game score from the character string "game", and
9729    record it as the history of the current game.  The game
9730    score is NOT assumed to start from the standard position.
9731    The display is not updated in any way.
9732    */
9733 void
9734 ParseGameHistory (char *game)
9735 {
9736     ChessMove moveType;
9737     int fromX, fromY, toX, toY, boardIndex;
9738     char promoChar;
9739     char *p, *q;
9740     char buf[MSG_SIZ];
9741
9742     if (appData.debugMode)
9743       fprintf(debugFP, "Parsing game history: %s\n", game);
9744
9745     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9746     gameInfo.site = StrSave(appData.icsHost);
9747     gameInfo.date = PGNDate();
9748     gameInfo.round = StrSave("-");
9749
9750     /* Parse out names of players */
9751     while (*game == ' ') game++;
9752     p = buf;
9753     while (*game != ' ') *p++ = *game++;
9754     *p = NULLCHAR;
9755     gameInfo.white = StrSave(buf);
9756     while (*game == ' ') game++;
9757     p = buf;
9758     while (*game != ' ' && *game != '\n') *p++ = *game++;
9759     *p = NULLCHAR;
9760     gameInfo.black = StrSave(buf);
9761
9762     /* Parse moves */
9763     boardIndex = blackPlaysFirst ? 1 : 0;
9764     yynewstr(game);
9765     for (;;) {
9766         yyboardindex = boardIndex;
9767         moveType = (ChessMove) Myylex();
9768         switch (moveType) {
9769           case IllegalMove:             /* maybe suicide chess, etc. */
9770   if (appData.debugMode) {
9771     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9772     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9773     setbuf(debugFP, NULL);
9774   }
9775           case WhitePromotion:
9776           case BlackPromotion:
9777           case WhiteNonPromotion:
9778           case BlackNonPromotion:
9779           case NormalMove:
9780           case FirstLeg:
9781           case WhiteCapturesEnPassant:
9782           case BlackCapturesEnPassant:
9783           case WhiteKingSideCastle:
9784           case WhiteQueenSideCastle:
9785           case BlackKingSideCastle:
9786           case BlackQueenSideCastle:
9787           case WhiteKingSideCastleWild:
9788           case WhiteQueenSideCastleWild:
9789           case BlackKingSideCastleWild:
9790           case BlackQueenSideCastleWild:
9791           /* PUSH Fabien */
9792           case WhiteHSideCastleFR:
9793           case WhiteASideCastleFR:
9794           case BlackHSideCastleFR:
9795           case BlackASideCastleFR:
9796           /* POP Fabien */
9797             fromX = currentMoveString[0] - AAA;
9798             fromY = currentMoveString[1] - ONE;
9799             toX = currentMoveString[2] - AAA;
9800             toY = currentMoveString[3] - ONE;
9801             promoChar = currentMoveString[4];
9802             break;
9803           case WhiteDrop:
9804           case BlackDrop:
9805             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9806             fromX = moveType == WhiteDrop ?
9807               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9808             (int) CharToPiece(ToLower(currentMoveString[0]));
9809             fromY = DROP_RANK;
9810             toX = currentMoveString[2] - AAA;
9811             toY = currentMoveString[3] - ONE;
9812             promoChar = NULLCHAR;
9813             break;
9814           case AmbiguousMove:
9815             /* bug? */
9816             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9817   if (appData.debugMode) {
9818     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9819     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9820     setbuf(debugFP, NULL);
9821   }
9822             DisplayError(buf, 0);
9823             return;
9824           case ImpossibleMove:
9825             /* bug? */
9826             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9827   if (appData.debugMode) {
9828     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9829     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9830     setbuf(debugFP, NULL);
9831   }
9832             DisplayError(buf, 0);
9833             return;
9834           case EndOfFile:
9835             if (boardIndex < backwardMostMove) {
9836                 /* Oops, gap.  How did that happen? */
9837                 DisplayError(_("Gap in move list"), 0);
9838                 return;
9839             }
9840             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9841             if (boardIndex > forwardMostMove) {
9842                 forwardMostMove = boardIndex;
9843             }
9844             return;
9845           case ElapsedTime:
9846             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9847                 strcat(parseList[boardIndex-1], " ");
9848                 strcat(parseList[boardIndex-1], yy_text);
9849             }
9850             continue;
9851           case Comment:
9852           case PGNTag:
9853           case NAG:
9854           default:
9855             /* ignore */
9856             continue;
9857           case WhiteWins:
9858           case BlackWins:
9859           case GameIsDrawn:
9860           case GameUnfinished:
9861             if (gameMode == IcsExamining) {
9862                 if (boardIndex < backwardMostMove) {
9863                     /* Oops, gap.  How did that happen? */
9864                     return;
9865                 }
9866                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9867                 return;
9868             }
9869             gameInfo.result = moveType;
9870             p = strchr(yy_text, '{');
9871             if (p == NULL) p = strchr(yy_text, '(');
9872             if (p == NULL) {
9873                 p = yy_text;
9874                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9875             } else {
9876                 q = strchr(p, *p == '{' ? '}' : ')');
9877                 if (q != NULL) *q = NULLCHAR;
9878                 p++;
9879             }
9880             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9881             gameInfo.resultDetails = StrSave(p);
9882             continue;
9883         }
9884         if (boardIndex >= forwardMostMove &&
9885             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9886             backwardMostMove = blackPlaysFirst ? 1 : 0;
9887             return;
9888         }
9889         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9890                                  fromY, fromX, toY, toX, promoChar,
9891                                  parseList[boardIndex]);
9892         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9893         /* currentMoveString is set as a side-effect of yylex */
9894         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9895         strcat(moveList[boardIndex], "\n");
9896         boardIndex++;
9897         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9898         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9899           case MT_NONE:
9900           case MT_STALEMATE:
9901           default:
9902             break;
9903           case MT_CHECK:
9904             if(!IS_SHOGI(gameInfo.variant))
9905                 strcat(parseList[boardIndex - 1], "+");
9906             break;
9907           case MT_CHECKMATE:
9908           case MT_STAINMATE:
9909             strcat(parseList[boardIndex - 1], "#");
9910             break;
9911         }
9912     }
9913 }
9914
9915
9916 /* Apply a move to the given board  */
9917 void
9918 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9919 {
9920   ChessSquare captured = board[toY][toX], piece, pawn, king, killed; int p, rookX, oldEP, epRank, berolina = 0;
9921   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9922
9923     /* [HGM] compute & store e.p. status and castling rights for new position */
9924     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9925
9926       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9927       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
9928       board[EP_STATUS] = EP_NONE;
9929       board[EP_FILE] = board[EP_RANK] = 100;
9930
9931   if (fromY == DROP_RANK) {
9932         /* must be first */
9933         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9934             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9935             return;
9936         }
9937         piece = board[toY][toX] = (ChessSquare) fromX;
9938   } else {
9939 //      ChessSquare victim;
9940       int i;
9941
9942       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9943 //           victim = board[killY][killX],
9944            killed = board[killY][killX],
9945            board[killY][killX] = EmptySquare,
9946            board[EP_STATUS] = EP_CAPTURE;
9947
9948       if( board[toY][toX] != EmptySquare ) {
9949            board[EP_STATUS] = EP_CAPTURE;
9950            if( (fromX != toX || fromY != toY) && // not igui!
9951                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9952                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9953                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9954            }
9955       }
9956
9957       pawn = board[fromY][fromX];
9958       if( pawn == WhiteLance || pawn == BlackLance ) {
9959            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
9960                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
9961                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
9962            }
9963       }
9964       if( pawn == WhitePawn ) {
9965            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9966                board[EP_STATUS] = EP_PAWN_MOVE;
9967            if( toY-fromY>=2) {
9968                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
9969                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9970                         gameInfo.variant != VariantBerolina || toX < fromX)
9971                       board[EP_STATUS] = toX | berolina;
9972                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9973                         gameInfo.variant != VariantBerolina || toX > fromX)
9974                       board[EP_STATUS] = toX;
9975            }
9976       } else
9977       if( pawn == BlackPawn ) {
9978            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9979                board[EP_STATUS] = EP_PAWN_MOVE;
9980            if( toY-fromY<= -2) {
9981                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
9982                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9983                         gameInfo.variant != VariantBerolina || toX < fromX)
9984                       board[EP_STATUS] = toX | berolina;
9985                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9986                         gameInfo.variant != VariantBerolina || toX > fromX)
9987                       board[EP_STATUS] = toX;
9988            }
9989        }
9990
9991        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
9992        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
9993        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
9994        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
9995
9996        for(i=0; i<nrCastlingRights; i++) {
9997            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9998               board[CASTLING][i] == toX   && castlingRank[i] == toY
9999              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10000        }
10001
10002        if(gameInfo.variant == VariantSChess) { // update virginity
10003            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10004            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10005            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10006            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10007        }
10008
10009      if (fromX == toX && fromY == toY) return;
10010
10011      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10012      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10013      if(gameInfo.variant == VariantKnightmate)
10014          king += (int) WhiteUnicorn - (int) WhiteKing;
10015
10016     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10017        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10018         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10019         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10020         board[EP_STATUS] = EP_NONE; // capture was fake!
10021     } else
10022     /* Code added by Tord: */
10023     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10024     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10025         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10026       board[EP_STATUS] = EP_NONE; // capture was fake!
10027       board[fromY][fromX] = EmptySquare;
10028       board[toY][toX] = EmptySquare;
10029       if((toX > fromX) != (piece == WhiteRook)) {
10030         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10031       } else {
10032         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10033       }
10034     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10035                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10036       board[EP_STATUS] = EP_NONE;
10037       board[fromY][fromX] = EmptySquare;
10038       board[toY][toX] = EmptySquare;
10039       if((toX > fromX) != (piece == BlackRook)) {
10040         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10041       } else {
10042         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10043       }
10044     /* End of code added by Tord */
10045
10046     } else if (board[fromY][fromX] == king
10047         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10048         && toY == fromY && toX > fromX+1) {
10049         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10050         board[fromY][toX-1] = board[fromY][rookX];
10051         board[fromY][rookX] = EmptySquare;
10052         board[fromY][fromX] = EmptySquare;
10053         board[toY][toX] = king;
10054     } else if (board[fromY][fromX] == king
10055         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10056                && toY == fromY && toX < fromX-1) {
10057         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10058         board[fromY][toX+1] = board[fromY][rookX];
10059         board[fromY][rookX] = EmptySquare;
10060         board[fromY][fromX] = EmptySquare;
10061         board[toY][toX] = king;
10062     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10063                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10064                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10065                ) {
10066         /* white pawn promotion */
10067         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10068         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10069             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10070         board[fromY][fromX] = EmptySquare;
10071     } else if ((fromY >= BOARD_HEIGHT>>1)
10072                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10073                && (toX != fromX)
10074                && gameInfo.variant != VariantXiangqi
10075                && gameInfo.variant != VariantBerolina
10076                && (pawn == WhitePawn)
10077                && (board[toY][toX] == EmptySquare)) {
10078         board[fromY][fromX] = EmptySquare;
10079         board[toY][toX] = piece;
10080         if(toY == epRank - 128 + 1)
10081             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10082         else
10083             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10084     } else if ((fromY == BOARD_HEIGHT-4)
10085                && (toX == fromX)
10086                && gameInfo.variant == VariantBerolina
10087                && (board[fromY][fromX] == WhitePawn)
10088                && (board[toY][toX] == EmptySquare)) {
10089         board[fromY][fromX] = EmptySquare;
10090         board[toY][toX] = WhitePawn;
10091         if(oldEP & EP_BEROLIN_A) {
10092                 captured = board[fromY][fromX-1];
10093                 board[fromY][fromX-1] = EmptySquare;
10094         }else{  captured = board[fromY][fromX+1];
10095                 board[fromY][fromX+1] = EmptySquare;
10096         }
10097     } else if (board[fromY][fromX] == king
10098         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10099                && toY == fromY && toX > fromX+1) {
10100         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10101         board[fromY][toX-1] = board[fromY][rookX];
10102         board[fromY][rookX] = EmptySquare;
10103         board[fromY][fromX] = EmptySquare;
10104         board[toY][toX] = king;
10105     } else if (board[fromY][fromX] == king
10106         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10107                && toY == fromY && toX < fromX-1) {
10108         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10109         board[fromY][toX+1] = board[fromY][rookX];
10110         board[fromY][rookX] = EmptySquare;
10111         board[fromY][fromX] = EmptySquare;
10112         board[toY][toX] = king;
10113     } else if (fromY == 7 && fromX == 3
10114                && board[fromY][fromX] == BlackKing
10115                && toY == 7 && toX == 5) {
10116         board[fromY][fromX] = EmptySquare;
10117         board[toY][toX] = BlackKing;
10118         board[fromY][7] = EmptySquare;
10119         board[toY][4] = BlackRook;
10120     } else if (fromY == 7 && fromX == 3
10121                && board[fromY][fromX] == BlackKing
10122                && toY == 7 && toX == 1) {
10123         board[fromY][fromX] = EmptySquare;
10124         board[toY][toX] = BlackKing;
10125         board[fromY][0] = EmptySquare;
10126         board[toY][2] = BlackRook;
10127     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10128                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10129                && toY < promoRank && promoChar
10130                ) {
10131         /* black pawn promotion */
10132         board[toY][toX] = CharToPiece(ToLower(promoChar));
10133         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10134             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10135         board[fromY][fromX] = EmptySquare;
10136     } else if ((fromY < BOARD_HEIGHT>>1)
10137                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10138                && (toX != fromX)
10139                && gameInfo.variant != VariantXiangqi
10140                && gameInfo.variant != VariantBerolina
10141                && (pawn == BlackPawn)
10142                && (board[toY][toX] == EmptySquare)) {
10143         board[fromY][fromX] = EmptySquare;
10144         board[toY][toX] = piece;
10145         if(toY == epRank - 128 - 1)
10146             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10147         else
10148             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10149     } else if ((fromY == 3)
10150                && (toX == fromX)
10151                && gameInfo.variant == VariantBerolina
10152                && (board[fromY][fromX] == BlackPawn)
10153                && (board[toY][toX] == EmptySquare)) {
10154         board[fromY][fromX] = EmptySquare;
10155         board[toY][toX] = BlackPawn;
10156         if(oldEP & EP_BEROLIN_A) {
10157                 captured = board[fromY][fromX-1];
10158                 board[fromY][fromX-1] = EmptySquare;
10159         }else{  captured = board[fromY][fromX+1];
10160                 board[fromY][fromX+1] = EmptySquare;
10161         }
10162     } else {
10163         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10164         board[fromY][fromX] = EmptySquare;
10165         board[toY][toX] = piece;
10166     }
10167   }
10168
10169     if (gameInfo.holdingsWidth != 0) {
10170
10171       /* !!A lot more code needs to be written to support holdings  */
10172       /* [HGM] OK, so I have written it. Holdings are stored in the */
10173       /* penultimate board files, so they are automaticlly stored   */
10174       /* in the game history.                                       */
10175       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10176                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10177         /* Delete from holdings, by decreasing count */
10178         /* and erasing image if necessary            */
10179         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10180         if(p < (int) BlackPawn) { /* white drop */
10181              p -= (int)WhitePawn;
10182                  p = PieceToNumber((ChessSquare)p);
10183              if(p >= gameInfo.holdingsSize) p = 0;
10184              if(--board[p][BOARD_WIDTH-2] <= 0)
10185                   board[p][BOARD_WIDTH-1] = EmptySquare;
10186              if((int)board[p][BOARD_WIDTH-2] < 0)
10187                         board[p][BOARD_WIDTH-2] = 0;
10188         } else {                  /* black drop */
10189              p -= (int)BlackPawn;
10190                  p = PieceToNumber((ChessSquare)p);
10191              if(p >= gameInfo.holdingsSize) p = 0;
10192              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10193                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10194              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10195                         board[BOARD_HEIGHT-1-p][1] = 0;
10196         }
10197       }
10198       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10199           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10200         /* [HGM] holdings: Add to holdings, if holdings exist */
10201         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10202                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10203                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10204         }
10205         p = (int) captured;
10206         if (p >= (int) BlackPawn) {
10207           p -= (int)BlackPawn;
10208           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10209                   /* Restore shogi-promoted piece to its original  first */
10210                   captured = (ChessSquare) (DEMOTED captured);
10211                   p = DEMOTED p;
10212           }
10213           p = PieceToNumber((ChessSquare)p);
10214           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10215           board[p][BOARD_WIDTH-2]++;
10216           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10217         } else {
10218           p -= (int)WhitePawn;
10219           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10220                   captured = (ChessSquare) (DEMOTED captured);
10221                   p = DEMOTED p;
10222           }
10223           p = PieceToNumber((ChessSquare)p);
10224           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10225           board[BOARD_HEIGHT-1-p][1]++;
10226           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10227         }
10228       }
10229     } else if (gameInfo.variant == VariantAtomic) {
10230       if (captured != EmptySquare) {
10231         int y, x;
10232         for (y = toY-1; y <= toY+1; y++) {
10233           for (x = toX-1; x <= toX+1; x++) {
10234             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10235                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10236               board[y][x] = EmptySquare;
10237             }
10238           }
10239         }
10240         board[toY][toX] = EmptySquare;
10241       }
10242     }
10243
10244     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10245         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10246     } else
10247     if(promoChar == '+') {
10248         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10249         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10250         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10251           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10252     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10253         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10254         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10255            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10256         board[toY][toX] = newPiece;
10257     }
10258     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10259                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10260         // [HGM] superchess: take promotion piece out of holdings
10261         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10262         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10263             if(!--board[k][BOARD_WIDTH-2])
10264                 board[k][BOARD_WIDTH-1] = EmptySquare;
10265         } else {
10266             if(!--board[BOARD_HEIGHT-1-k][1])
10267                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10268         }
10269     }
10270 }
10271
10272 /* Updates forwardMostMove */
10273 void
10274 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10275 {
10276     int x = toX, y = toY;
10277     char *s = parseList[forwardMostMove];
10278     ChessSquare p = boards[forwardMostMove][toY][toX];
10279 //    forwardMostMove++; // [HGM] bare: moved downstream
10280
10281     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10282     (void) CoordsToAlgebraic(boards[forwardMostMove],
10283                              PosFlags(forwardMostMove),
10284                              fromY, fromX, y, x, promoChar,
10285                              s);
10286     if(killX >= 0 && killY >= 0)
10287         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10288
10289     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10290         int timeLeft; static int lastLoadFlag=0; int king, piece;
10291         piece = boards[forwardMostMove][fromY][fromX];
10292         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10293         if(gameInfo.variant == VariantKnightmate)
10294             king += (int) WhiteUnicorn - (int) WhiteKing;
10295         if(forwardMostMove == 0) {
10296             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10297                 fprintf(serverMoves, "%s;", UserName());
10298             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10299                 fprintf(serverMoves, "%s;", second.tidy);
10300             fprintf(serverMoves, "%s;", first.tidy);
10301             if(gameMode == MachinePlaysWhite)
10302                 fprintf(serverMoves, "%s;", UserName());
10303             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10304                 fprintf(serverMoves, "%s;", second.tidy);
10305         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10306         lastLoadFlag = loadFlag;
10307         // print base move
10308         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10309         // print castling suffix
10310         if( toY == fromY && piece == king ) {
10311             if(toX-fromX > 1)
10312                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10313             if(fromX-toX >1)
10314                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10315         }
10316         // e.p. suffix
10317         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10318              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10319              boards[forwardMostMove][toY][toX] == EmptySquare
10320              && fromX != toX && fromY != toY)
10321                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10322         // promotion suffix
10323         if(promoChar != NULLCHAR) {
10324             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10325                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10326                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10327             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10328         }
10329         if(!loadFlag) {
10330                 char buf[MOVE_LEN*2], *p; int len;
10331             fprintf(serverMoves, "/%d/%d",
10332                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10333             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10334             else                      timeLeft = blackTimeRemaining/1000;
10335             fprintf(serverMoves, "/%d", timeLeft);
10336                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10337                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10338                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10339                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10340             fprintf(serverMoves, "/%s", buf);
10341         }
10342         fflush(serverMoves);
10343     }
10344
10345     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10346         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10347       return;
10348     }
10349     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10350     if (commentList[forwardMostMove+1] != NULL) {
10351         free(commentList[forwardMostMove+1]);
10352         commentList[forwardMostMove+1] = NULL;
10353     }
10354     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10355     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10356     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10357     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10358     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10359     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10360     adjustedClock = FALSE;
10361     gameInfo.result = GameUnfinished;
10362     if (gameInfo.resultDetails != NULL) {
10363         free(gameInfo.resultDetails);
10364         gameInfo.resultDetails = NULL;
10365     }
10366     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10367                               moveList[forwardMostMove - 1]);
10368     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10369       case MT_NONE:
10370       case MT_STALEMATE:
10371       default:
10372         break;
10373       case MT_CHECK:
10374         if(!IS_SHOGI(gameInfo.variant))
10375             strcat(parseList[forwardMostMove - 1], "+");
10376         break;
10377       case MT_CHECKMATE:
10378       case MT_STAINMATE:
10379         strcat(parseList[forwardMostMove - 1], "#");
10380         break;
10381     }
10382 }
10383
10384 /* Updates currentMove if not pausing */
10385 void
10386 ShowMove (int fromX, int fromY, int toX, int toY)
10387 {
10388     int instant = (gameMode == PlayFromGameFile) ?
10389         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10390     if(appData.noGUI) return;
10391     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10392         if (!instant) {
10393             if (forwardMostMove == currentMove + 1) {
10394                 AnimateMove(boards[forwardMostMove - 1],
10395                             fromX, fromY, toX, toY);
10396             }
10397         }
10398         currentMove = forwardMostMove;
10399     }
10400
10401     killX = killY = -1; // [HGM] lion: used up
10402
10403     if (instant) return;
10404
10405     DisplayMove(currentMove - 1);
10406     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10407             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10408                 SetHighlights(fromX, fromY, toX, toY);
10409             }
10410     }
10411     DrawPosition(FALSE, boards[currentMove]);
10412     DisplayBothClocks();
10413     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10414 }
10415
10416 void
10417 SendEgtPath (ChessProgramState *cps)
10418 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10419         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10420
10421         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10422
10423         while(*p) {
10424             char c, *q = name+1, *r, *s;
10425
10426             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10427             while(*p && *p != ',') *q++ = *p++;
10428             *q++ = ':'; *q = 0;
10429             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10430                 strcmp(name, ",nalimov:") == 0 ) {
10431                 // take nalimov path from the menu-changeable option first, if it is defined
10432               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10433                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10434             } else
10435             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10436                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10437                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10438                 s = r = StrStr(s, ":") + 1; // beginning of path info
10439                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10440                 c = *r; *r = 0;             // temporarily null-terminate path info
10441                     *--q = 0;               // strip of trailig ':' from name
10442                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10443                 *r = c;
10444                 SendToProgram(buf,cps);     // send egtbpath command for this format
10445             }
10446             if(*p == ',') p++; // read away comma to position for next format name
10447         }
10448 }
10449
10450 static int
10451 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10452 {
10453       int width = 8, height = 8, holdings = 0;             // most common sizes
10454       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10455       // correct the deviations default for each variant
10456       if( v == VariantXiangqi ) width = 9,  height = 10;
10457       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10458       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10459       if( v == VariantCapablanca || v == VariantCapaRandom ||
10460           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10461                                 width = 10;
10462       if( v == VariantCourier ) width = 12;
10463       if( v == VariantSuper )                            holdings = 8;
10464       if( v == VariantGreat )   width = 10,              holdings = 8;
10465       if( v == VariantSChess )                           holdings = 7;
10466       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10467       if( v == VariantChuChess) width = 10, height = 10;
10468       if( v == VariantChu )     width = 12, height = 12;
10469       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10470              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10471              holdingsSize >= 0 && holdingsSize != holdings;
10472 }
10473
10474 char variantError[MSG_SIZ];
10475
10476 char *
10477 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10478 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10479       char *p, *variant = VariantName(v);
10480       static char b[MSG_SIZ];
10481       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10482            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10483                                                holdingsSize, variant); // cook up sized variant name
10484            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10485            if(StrStr(list, b) == NULL) {
10486                // specific sized variant not known, check if general sizing allowed
10487                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10488                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10489                             boardWidth, boardHeight, holdingsSize, engine);
10490                    return NULL;
10491                }
10492                /* [HGM] here we really should compare with the maximum supported board size */
10493            }
10494       } else snprintf(b, MSG_SIZ,"%s", variant);
10495       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10496       p = StrStr(list, b);
10497       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10498       if(p == NULL) {
10499           // occurs not at all in list, or only as sub-string
10500           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10501           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10502               int l = strlen(variantError);
10503               char *q;
10504               while(p != list && p[-1] != ',') p--;
10505               q = strchr(p, ',');
10506               if(q) *q = NULLCHAR;
10507               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10508               if(q) *q= ',';
10509           }
10510           return NULL;
10511       }
10512       return b;
10513 }
10514
10515 void
10516 InitChessProgram (ChessProgramState *cps, int setup)
10517 /* setup needed to setup FRC opening position */
10518 {
10519     char buf[MSG_SIZ], *b;
10520     if (appData.noChessProgram) return;
10521     hintRequested = FALSE;
10522     bookRequested = FALSE;
10523
10524     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10525     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10526     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10527     if(cps->memSize) { /* [HGM] memory */
10528       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10529         SendToProgram(buf, cps);
10530     }
10531     SendEgtPath(cps); /* [HGM] EGT */
10532     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10533       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10534         SendToProgram(buf, cps);
10535     }
10536
10537     setboardSpoiledMachineBlack = FALSE;
10538     SendToProgram(cps->initString, cps);
10539     if (gameInfo.variant != VariantNormal &&
10540         gameInfo.variant != VariantLoadable
10541         /* [HGM] also send variant if board size non-standard */
10542         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10543
10544       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10545                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10546       if (b == NULL) {
10547         VariantClass v;
10548         char c, *q = cps->variants, *p = strchr(q, ',');
10549         if(p) *p = NULLCHAR;
10550         v = StringToVariant(q);
10551         DisplayError(variantError, 0);
10552         if(v != VariantUnknown && cps == &first) {
10553             int w, h, s;
10554             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10555                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10556             ASSIGN(appData.variant, q);
10557             Reset(TRUE, FALSE);
10558         }
10559         if(p) *p = ',';
10560         return;
10561       }
10562
10563       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10564       SendToProgram(buf, cps);
10565     }
10566     currentlyInitializedVariant = gameInfo.variant;
10567
10568     /* [HGM] send opening position in FRC to first engine */
10569     if(setup) {
10570           SendToProgram("force\n", cps);
10571           SendBoard(cps, 0);
10572           /* engine is now in force mode! Set flag to wake it up after first move. */
10573           setboardSpoiledMachineBlack = 1;
10574     }
10575
10576     if (cps->sendICS) {
10577       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10578       SendToProgram(buf, cps);
10579     }
10580     cps->maybeThinking = FALSE;
10581     cps->offeredDraw = 0;
10582     if (!appData.icsActive) {
10583         SendTimeControl(cps, movesPerSession, timeControl,
10584                         timeIncrement, appData.searchDepth,
10585                         searchTime);
10586     }
10587     if (appData.showThinking
10588         // [HGM] thinking: four options require thinking output to be sent
10589         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10590                                 ) {
10591         SendToProgram("post\n", cps);
10592     }
10593     SendToProgram("hard\n", cps);
10594     if (!appData.ponderNextMove) {
10595         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10596            it without being sure what state we are in first.  "hard"
10597            is not a toggle, so that one is OK.
10598          */
10599         SendToProgram("easy\n", cps);
10600     }
10601     if (cps->usePing) {
10602       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10603       SendToProgram(buf, cps);
10604     }
10605     cps->initDone = TRUE;
10606     ClearEngineOutputPane(cps == &second);
10607 }
10608
10609
10610 void
10611 ResendOptions (ChessProgramState *cps)
10612 { // send the stored value of the options
10613   int i;
10614   char buf[MSG_SIZ];
10615   Option *opt = cps->option;
10616   for(i=0; i<cps->nrOptions; i++, opt++) {
10617       switch(opt->type) {
10618         case Spin:
10619         case Slider:
10620         case CheckBox:
10621             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10622           break;
10623         case ComboBox:
10624           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10625           break;
10626         default:
10627             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10628           break;
10629         case Button:
10630         case SaveButton:
10631           continue;
10632       }
10633       SendToProgram(buf, cps);
10634   }
10635 }
10636
10637 void
10638 StartChessProgram (ChessProgramState *cps)
10639 {
10640     char buf[MSG_SIZ];
10641     int err;
10642
10643     if (appData.noChessProgram) return;
10644     cps->initDone = FALSE;
10645
10646     if (strcmp(cps->host, "localhost") == 0) {
10647         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10648     } else if (*appData.remoteShell == NULLCHAR) {
10649         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10650     } else {
10651         if (*appData.remoteUser == NULLCHAR) {
10652           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10653                     cps->program);
10654         } else {
10655           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10656                     cps->host, appData.remoteUser, cps->program);
10657         }
10658         err = StartChildProcess(buf, "", &cps->pr);
10659     }
10660
10661     if (err != 0) {
10662       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10663         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10664         if(cps != &first) return;
10665         appData.noChessProgram = TRUE;
10666         ThawUI();
10667         SetNCPMode();
10668 //      DisplayFatalError(buf, err, 1);
10669 //      cps->pr = NoProc;
10670 //      cps->isr = NULL;
10671         return;
10672     }
10673
10674     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10675     if (cps->protocolVersion > 1) {
10676       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10677       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10678         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10679         cps->comboCnt = 0;  //                and values of combo boxes
10680       }
10681       SendToProgram(buf, cps);
10682       if(cps->reload) ResendOptions(cps);
10683     } else {
10684       SendToProgram("xboard\n", cps);
10685     }
10686 }
10687
10688 void
10689 TwoMachinesEventIfReady P((void))
10690 {
10691   static int curMess = 0;
10692   if (first.lastPing != first.lastPong) {
10693     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10694     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10695     return;
10696   }
10697   if (second.lastPing != second.lastPong) {
10698     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10699     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10700     return;
10701   }
10702   DisplayMessage("", ""); curMess = 0;
10703   TwoMachinesEvent();
10704 }
10705
10706 char *
10707 MakeName (char *template)
10708 {
10709     time_t clock;
10710     struct tm *tm;
10711     static char buf[MSG_SIZ];
10712     char *p = buf;
10713     int i;
10714
10715     clock = time((time_t *)NULL);
10716     tm = localtime(&clock);
10717
10718     while(*p++ = *template++) if(p[-1] == '%') {
10719         switch(*template++) {
10720           case 0:   *p = 0; return buf;
10721           case 'Y': i = tm->tm_year+1900; break;
10722           case 'y': i = tm->tm_year-100; break;
10723           case 'M': i = tm->tm_mon+1; break;
10724           case 'd': i = tm->tm_mday; break;
10725           case 'h': i = tm->tm_hour; break;
10726           case 'm': i = tm->tm_min; break;
10727           case 's': i = tm->tm_sec; break;
10728           default:  i = 0;
10729         }
10730         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10731     }
10732     return buf;
10733 }
10734
10735 int
10736 CountPlayers (char *p)
10737 {
10738     int n = 0;
10739     while(p = strchr(p, '\n')) p++, n++; // count participants
10740     return n;
10741 }
10742
10743 FILE *
10744 WriteTourneyFile (char *results, FILE *f)
10745 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10746     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10747     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10748         // create a file with tournament description
10749         fprintf(f, "-participants {%s}\n", appData.participants);
10750         fprintf(f, "-seedBase %d\n", appData.seedBase);
10751         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10752         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10753         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10754         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10755         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10756         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10757         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10758         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10759         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10760         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10761         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10762         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10763         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10764         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10765         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10766         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10767         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10768         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10769         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10770         fprintf(f, "-smpCores %d\n", appData.smpCores);
10771         if(searchTime > 0)
10772                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10773         else {
10774                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10775                 fprintf(f, "-tc %s\n", appData.timeControl);
10776                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10777         }
10778         fprintf(f, "-results \"%s\"\n", results);
10779     }
10780     return f;
10781 }
10782
10783 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10784
10785 void
10786 Substitute (char *participants, int expunge)
10787 {
10788     int i, changed, changes=0, nPlayers=0;
10789     char *p, *q, *r, buf[MSG_SIZ];
10790     if(participants == NULL) return;
10791     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10792     r = p = participants; q = appData.participants;
10793     while(*p && *p == *q) {
10794         if(*p == '\n') r = p+1, nPlayers++;
10795         p++; q++;
10796     }
10797     if(*p) { // difference
10798         while(*p && *p++ != '\n');
10799         while(*q && *q++ != '\n');
10800       changed = nPlayers;
10801         changes = 1 + (strcmp(p, q) != 0);
10802     }
10803     if(changes == 1) { // a single engine mnemonic was changed
10804         q = r; while(*q) nPlayers += (*q++ == '\n');
10805         p = buf; while(*r && (*p = *r++) != '\n') p++;
10806         *p = NULLCHAR;
10807         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10808         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10809         if(mnemonic[i]) { // The substitute is valid
10810             FILE *f;
10811             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10812                 flock(fileno(f), LOCK_EX);
10813                 ParseArgsFromFile(f);
10814                 fseek(f, 0, SEEK_SET);
10815                 FREE(appData.participants); appData.participants = participants;
10816                 if(expunge) { // erase results of replaced engine
10817                     int len = strlen(appData.results), w, b, dummy;
10818                     for(i=0; i<len; i++) {
10819                         Pairing(i, nPlayers, &w, &b, &dummy);
10820                         if((w == changed || b == changed) && appData.results[i] == '*') {
10821                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10822                             fclose(f);
10823                             return;
10824                         }
10825                     }
10826                     for(i=0; i<len; i++) {
10827                         Pairing(i, nPlayers, &w, &b, &dummy);
10828                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10829                     }
10830                 }
10831                 WriteTourneyFile(appData.results, f);
10832                 fclose(f); // release lock
10833                 return;
10834             }
10835         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10836     }
10837     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10838     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10839     free(participants);
10840     return;
10841 }
10842
10843 int
10844 CheckPlayers (char *participants)
10845 {
10846         int i;
10847         char buf[MSG_SIZ], *p;
10848         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10849         while(p = strchr(participants, '\n')) {
10850             *p = NULLCHAR;
10851             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10852             if(!mnemonic[i]) {
10853                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10854                 *p = '\n';
10855                 DisplayError(buf, 0);
10856                 return 1;
10857             }
10858             *p = '\n';
10859             participants = p + 1;
10860         }
10861         return 0;
10862 }
10863
10864 int
10865 CreateTourney (char *name)
10866 {
10867         FILE *f;
10868         if(matchMode && strcmp(name, appData.tourneyFile)) {
10869              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10870         }
10871         if(name[0] == NULLCHAR) {
10872             if(appData.participants[0])
10873                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10874             return 0;
10875         }
10876         f = fopen(name, "r");
10877         if(f) { // file exists
10878             ASSIGN(appData.tourneyFile, name);
10879             ParseArgsFromFile(f); // parse it
10880         } else {
10881             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10882             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10883                 DisplayError(_("Not enough participants"), 0);
10884                 return 0;
10885             }
10886             if(CheckPlayers(appData.participants)) return 0;
10887             ASSIGN(appData.tourneyFile, name);
10888             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10889             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10890         }
10891         fclose(f);
10892         appData.noChessProgram = FALSE;
10893         appData.clockMode = TRUE;
10894         SetGNUMode();
10895         return 1;
10896 }
10897
10898 int
10899 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10900 {
10901     char buf[MSG_SIZ], *p, *q;
10902     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10903     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10904     skip = !all && group[0]; // if group requested, we start in skip mode
10905     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10906         p = names; q = buf; header = 0;
10907         while(*p && *p != '\n') *q++ = *p++;
10908         *q = 0;
10909         if(*p == '\n') p++;
10910         if(buf[0] == '#') {
10911             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10912             depth++; // we must be entering a new group
10913             if(all) continue; // suppress printing group headers when complete list requested
10914             header = 1;
10915             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10916         }
10917         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10918         if(engineList[i]) free(engineList[i]);
10919         engineList[i] = strdup(buf);
10920         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10921         if(engineMnemonic[i]) free(engineMnemonic[i]);
10922         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10923             strcat(buf, " (");
10924             sscanf(q + 8, "%s", buf + strlen(buf));
10925             strcat(buf, ")");
10926         }
10927         engineMnemonic[i] = strdup(buf);
10928         i++;
10929     }
10930     engineList[i] = engineMnemonic[i] = NULL;
10931     return i;
10932 }
10933
10934 // following implemented as macro to avoid type limitations
10935 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10936
10937 void
10938 SwapEngines (int n)
10939 {   // swap settings for first engine and other engine (so far only some selected options)
10940     int h;
10941     char *p;
10942     if(n == 0) return;
10943     SWAP(directory, p)
10944     SWAP(chessProgram, p)
10945     SWAP(isUCI, h)
10946     SWAP(hasOwnBookUCI, h)
10947     SWAP(protocolVersion, h)
10948     SWAP(reuse, h)
10949     SWAP(scoreIsAbsolute, h)
10950     SWAP(timeOdds, h)
10951     SWAP(logo, p)
10952     SWAP(pgnName, p)
10953     SWAP(pvSAN, h)
10954     SWAP(engOptions, p)
10955     SWAP(engInitString, p)
10956     SWAP(computerString, p)
10957     SWAP(features, p)
10958     SWAP(fenOverride, p)
10959     SWAP(NPS, h)
10960     SWAP(accumulateTC, h)
10961     SWAP(drawDepth, h)
10962     SWAP(host, p)
10963     SWAP(pseudo, h)
10964 }
10965
10966 int
10967 GetEngineLine (char *s, int n)
10968 {
10969     int i;
10970     char buf[MSG_SIZ];
10971     extern char *icsNames;
10972     if(!s || !*s) return 0;
10973     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10974     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10975     if(!mnemonic[i]) return 0;
10976     if(n == 11) return 1; // just testing if there was a match
10977     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10978     if(n == 1) SwapEngines(n);
10979     ParseArgsFromString(buf);
10980     if(n == 1) SwapEngines(n);
10981     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10982         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10983         ParseArgsFromString(buf);
10984     }
10985     return 1;
10986 }
10987
10988 int
10989 SetPlayer (int player, char *p)
10990 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10991     int i;
10992     char buf[MSG_SIZ], *engineName;
10993     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10994     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10995     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10996     if(mnemonic[i]) {
10997         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10998         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10999         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11000         ParseArgsFromString(buf);
11001     } else { // no engine with this nickname is installed!
11002         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11003         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11004         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11005         ModeHighlight();
11006         DisplayError(buf, 0);
11007         return 0;
11008     }
11009     free(engineName);
11010     return i;
11011 }
11012
11013 char *recentEngines;
11014
11015 void
11016 RecentEngineEvent (int nr)
11017 {
11018     int n;
11019 //    SwapEngines(1); // bump first to second
11020 //    ReplaceEngine(&second, 1); // and load it there
11021     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11022     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11023     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11024         ReplaceEngine(&first, 0);
11025         FloatToFront(&appData.recentEngineList, command[n]);
11026     }
11027 }
11028
11029 int
11030 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11031 {   // determine players from game number
11032     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11033
11034     if(appData.tourneyType == 0) {
11035         roundsPerCycle = (nPlayers - 1) | 1;
11036         pairingsPerRound = nPlayers / 2;
11037     } else if(appData.tourneyType > 0) {
11038         roundsPerCycle = nPlayers - appData.tourneyType;
11039         pairingsPerRound = appData.tourneyType;
11040     }
11041     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11042     gamesPerCycle = gamesPerRound * roundsPerCycle;
11043     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11044     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11045     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11046     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11047     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11048     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11049
11050     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11051     if(appData.roundSync) *syncInterval = gamesPerRound;
11052
11053     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11054
11055     if(appData.tourneyType == 0) {
11056         if(curPairing == (nPlayers-1)/2 ) {
11057             *whitePlayer = curRound;
11058             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11059         } else {
11060             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11061             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11062             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11063             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11064         }
11065     } else if(appData.tourneyType > 1) {
11066         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11067         *whitePlayer = curRound + appData.tourneyType;
11068     } else if(appData.tourneyType > 0) {
11069         *whitePlayer = curPairing;
11070         *blackPlayer = curRound + appData.tourneyType;
11071     }
11072
11073     // take care of white/black alternation per round.
11074     // For cycles and games this is already taken care of by default, derived from matchGame!
11075     return curRound & 1;
11076 }
11077
11078 int
11079 NextTourneyGame (int nr, int *swapColors)
11080 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11081     char *p, *q;
11082     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11083     FILE *tf;
11084     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11085     tf = fopen(appData.tourneyFile, "r");
11086     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11087     ParseArgsFromFile(tf); fclose(tf);
11088     InitTimeControls(); // TC might be altered from tourney file
11089
11090     nPlayers = CountPlayers(appData.participants); // count participants
11091     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11092     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11093
11094     if(syncInterval) {
11095         p = q = appData.results;
11096         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11097         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11098             DisplayMessage(_("Waiting for other game(s)"),"");
11099             waitingForGame = TRUE;
11100             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11101             return 0;
11102         }
11103         waitingForGame = FALSE;
11104     }
11105
11106     if(appData.tourneyType < 0) {
11107         if(nr>=0 && !pairingReceived) {
11108             char buf[1<<16];
11109             if(pairing.pr == NoProc) {
11110                 if(!appData.pairingEngine[0]) {
11111                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11112                     return 0;
11113                 }
11114                 StartChessProgram(&pairing); // starts the pairing engine
11115             }
11116             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11117             SendToProgram(buf, &pairing);
11118             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11119             SendToProgram(buf, &pairing);
11120             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11121         }
11122         pairingReceived = 0;                              // ... so we continue here
11123         *swapColors = 0;
11124         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11125         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11126         matchGame = 1; roundNr = nr / syncInterval + 1;
11127     }
11128
11129     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11130
11131     // redefine engines, engine dir, etc.
11132     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11133     if(first.pr == NoProc) {
11134       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11135       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11136     }
11137     if(second.pr == NoProc) {
11138       SwapEngines(1);
11139       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11140       SwapEngines(1);         // and make that valid for second engine by swapping
11141       InitEngine(&second, 1);
11142     }
11143     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11144     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11145     return OK;
11146 }
11147
11148 void
11149 NextMatchGame ()
11150 {   // performs game initialization that does not invoke engines, and then tries to start the game
11151     int res, firstWhite, swapColors = 0;
11152     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11153     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
11154         char buf[MSG_SIZ];
11155         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11156         if(strcmp(buf, currentDebugFile)) { // name has changed
11157             FILE *f = fopen(buf, "w");
11158             if(f) { // if opening the new file failed, just keep using the old one
11159                 ASSIGN(currentDebugFile, buf);
11160                 fclose(debugFP);
11161                 debugFP = f;
11162             }
11163             if(appData.serverFileName) {
11164                 if(serverFP) fclose(serverFP);
11165                 serverFP = fopen(appData.serverFileName, "w");
11166                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11167                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11168             }
11169         }
11170     }
11171     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11172     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11173     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11174     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11175     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11176     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11177     Reset(FALSE, first.pr != NoProc);
11178     res = LoadGameOrPosition(matchGame); // setup game
11179     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11180     if(!res) return; // abort when bad game/pos file
11181     TwoMachinesEvent();
11182 }
11183
11184 void
11185 UserAdjudicationEvent (int result)
11186 {
11187     ChessMove gameResult = GameIsDrawn;
11188
11189     if( result > 0 ) {
11190         gameResult = WhiteWins;
11191     }
11192     else if( result < 0 ) {
11193         gameResult = BlackWins;
11194     }
11195
11196     if( gameMode == TwoMachinesPlay ) {
11197         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11198     }
11199 }
11200
11201
11202 // [HGM] save: calculate checksum of game to make games easily identifiable
11203 int
11204 StringCheckSum (char *s)
11205 {
11206         int i = 0;
11207         if(s==NULL) return 0;
11208         while(*s) i = i*259 + *s++;
11209         return i;
11210 }
11211
11212 int
11213 GameCheckSum ()
11214 {
11215         int i, sum=0;
11216         for(i=backwardMostMove; i<forwardMostMove; i++) {
11217                 sum += pvInfoList[i].depth;
11218                 sum += StringCheckSum(parseList[i]);
11219                 sum += StringCheckSum(commentList[i]);
11220                 sum *= 261;
11221         }
11222         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11223         return sum + StringCheckSum(commentList[i]);
11224 } // end of save patch
11225
11226 void
11227 GameEnds (ChessMove result, char *resultDetails, int whosays)
11228 {
11229     GameMode nextGameMode;
11230     int isIcsGame;
11231     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11232
11233     if(endingGame) return; /* [HGM] crash: forbid recursion */
11234     endingGame = 1;
11235     if(twoBoards) { // [HGM] dual: switch back to one board
11236         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11237         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11238     }
11239     if (appData.debugMode) {
11240       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11241               result, resultDetails ? resultDetails : "(null)", whosays);
11242     }
11243
11244     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11245
11246     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11247
11248     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11249         /* If we are playing on ICS, the server decides when the
11250            game is over, but the engine can offer to draw, claim
11251            a draw, or resign.
11252          */
11253 #if ZIPPY
11254         if (appData.zippyPlay && first.initDone) {
11255             if (result == GameIsDrawn) {
11256                 /* In case draw still needs to be claimed */
11257                 SendToICS(ics_prefix);
11258                 SendToICS("draw\n");
11259             } else if (StrCaseStr(resultDetails, "resign")) {
11260                 SendToICS(ics_prefix);
11261                 SendToICS("resign\n");
11262             }
11263         }
11264 #endif
11265         endingGame = 0; /* [HGM] crash */
11266         return;
11267     }
11268
11269     /* If we're loading the game from a file, stop */
11270     if (whosays == GE_FILE) {
11271       (void) StopLoadGameTimer();
11272       gameFileFP = NULL;
11273     }
11274
11275     /* Cancel draw offers */
11276     first.offeredDraw = second.offeredDraw = 0;
11277
11278     /* If this is an ICS game, only ICS can really say it's done;
11279        if not, anyone can. */
11280     isIcsGame = (gameMode == IcsPlayingWhite ||
11281                  gameMode == IcsPlayingBlack ||
11282                  gameMode == IcsObserving    ||
11283                  gameMode == IcsExamining);
11284
11285     if (!isIcsGame || whosays == GE_ICS) {
11286         /* OK -- not an ICS game, or ICS said it was done */
11287         StopClocks();
11288         if (!isIcsGame && !appData.noChessProgram)
11289           SetUserThinkingEnables();
11290
11291         /* [HGM] if a machine claims the game end we verify this claim */
11292         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11293             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11294                 char claimer;
11295                 ChessMove trueResult = (ChessMove) -1;
11296
11297                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11298                                             first.twoMachinesColor[0] :
11299                                             second.twoMachinesColor[0] ;
11300
11301                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11302                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11303                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11304                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11305                 } else
11306                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11307                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11308                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11309                 } else
11310                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11311                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11312                 }
11313
11314                 // now verify win claims, but not in drop games, as we don't understand those yet
11315                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11316                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11317                     (result == WhiteWins && claimer == 'w' ||
11318                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11319                       if (appData.debugMode) {
11320                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11321                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11322                       }
11323                       if(result != trueResult) {
11324                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11325                               result = claimer == 'w' ? BlackWins : WhiteWins;
11326                               resultDetails = buf;
11327                       }
11328                 } else
11329                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11330                     && (forwardMostMove <= backwardMostMove ||
11331                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11332                         (claimer=='b')==(forwardMostMove&1))
11333                                                                                   ) {
11334                       /* [HGM] verify: draws that were not flagged are false claims */
11335                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11336                       result = claimer == 'w' ? BlackWins : WhiteWins;
11337                       resultDetails = buf;
11338                 }
11339                 /* (Claiming a loss is accepted no questions asked!) */
11340             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11341                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11342                 result = GameUnfinished;
11343                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11344             }
11345             /* [HGM] bare: don't allow bare King to win */
11346             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11347                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11348                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11349                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11350                && result != GameIsDrawn)
11351             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11352                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11353                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11354                         if(p >= 0 && p <= (int)WhiteKing) k++;
11355                 }
11356                 if (appData.debugMode) {
11357                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11358                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11359                 }
11360                 if(k <= 1) {
11361                         result = GameIsDrawn;
11362                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11363                         resultDetails = buf;
11364                 }
11365             }
11366         }
11367
11368
11369         if(serverMoves != NULL && !loadFlag) { char c = '=';
11370             if(result==WhiteWins) c = '+';
11371             if(result==BlackWins) c = '-';
11372             if(resultDetails != NULL)
11373                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11374         }
11375         if (resultDetails != NULL) {
11376             gameInfo.result = result;
11377             gameInfo.resultDetails = StrSave(resultDetails);
11378
11379             /* display last move only if game was not loaded from file */
11380             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11381                 DisplayMove(currentMove - 1);
11382
11383             if (forwardMostMove != 0) {
11384                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11385                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11386                                                                 ) {
11387                     if (*appData.saveGameFile != NULLCHAR) {
11388                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11389                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11390                         else
11391                         SaveGameToFile(appData.saveGameFile, TRUE);
11392                     } else if (appData.autoSaveGames) {
11393                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11394                     }
11395                     if (*appData.savePositionFile != NULLCHAR) {
11396                         SavePositionToFile(appData.savePositionFile);
11397                     }
11398                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11399                 }
11400             }
11401
11402             /* Tell program how game ended in case it is learning */
11403             /* [HGM] Moved this to after saving the PGN, just in case */
11404             /* engine died and we got here through time loss. In that */
11405             /* case we will get a fatal error writing the pipe, which */
11406             /* would otherwise lose us the PGN.                       */
11407             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11408             /* output during GameEnds should never be fatal anymore   */
11409             if (gameMode == MachinePlaysWhite ||
11410                 gameMode == MachinePlaysBlack ||
11411                 gameMode == TwoMachinesPlay ||
11412                 gameMode == IcsPlayingWhite ||
11413                 gameMode == IcsPlayingBlack ||
11414                 gameMode == BeginningOfGame) {
11415                 char buf[MSG_SIZ];
11416                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11417                         resultDetails);
11418                 if (first.pr != NoProc) {
11419                     SendToProgram(buf, &first);
11420                 }
11421                 if (second.pr != NoProc &&
11422                     gameMode == TwoMachinesPlay) {
11423                     SendToProgram(buf, &second);
11424                 }
11425             }
11426         }
11427
11428         if (appData.icsActive) {
11429             if (appData.quietPlay &&
11430                 (gameMode == IcsPlayingWhite ||
11431                  gameMode == IcsPlayingBlack)) {
11432                 SendToICS(ics_prefix);
11433                 SendToICS("set shout 1\n");
11434             }
11435             nextGameMode = IcsIdle;
11436             ics_user_moved = FALSE;
11437             /* clean up premove.  It's ugly when the game has ended and the
11438              * premove highlights are still on the board.
11439              */
11440             if (gotPremove) {
11441               gotPremove = FALSE;
11442               ClearPremoveHighlights();
11443               DrawPosition(FALSE, boards[currentMove]);
11444             }
11445             if (whosays == GE_ICS) {
11446                 switch (result) {
11447                 case WhiteWins:
11448                     if (gameMode == IcsPlayingWhite)
11449                         PlayIcsWinSound();
11450                     else if(gameMode == IcsPlayingBlack)
11451                         PlayIcsLossSound();
11452                     break;
11453                 case BlackWins:
11454                     if (gameMode == IcsPlayingBlack)
11455                         PlayIcsWinSound();
11456                     else if(gameMode == IcsPlayingWhite)
11457                         PlayIcsLossSound();
11458                     break;
11459                 case GameIsDrawn:
11460                     PlayIcsDrawSound();
11461                     break;
11462                 default:
11463                     PlayIcsUnfinishedSound();
11464                 }
11465             }
11466             if(appData.quitNext) { ExitEvent(0); return; }
11467         } else if (gameMode == EditGame ||
11468                    gameMode == PlayFromGameFile ||
11469                    gameMode == AnalyzeMode ||
11470                    gameMode == AnalyzeFile) {
11471             nextGameMode = gameMode;
11472         } else {
11473             nextGameMode = EndOfGame;
11474         }
11475         pausing = FALSE;
11476         ModeHighlight();
11477     } else {
11478         nextGameMode = gameMode;
11479     }
11480
11481     if (appData.noChessProgram) {
11482         gameMode = nextGameMode;
11483         ModeHighlight();
11484         endingGame = 0; /* [HGM] crash */
11485         return;
11486     }
11487
11488     if (first.reuse) {
11489         /* Put first chess program into idle state */
11490         if (first.pr != NoProc &&
11491             (gameMode == MachinePlaysWhite ||
11492              gameMode == MachinePlaysBlack ||
11493              gameMode == TwoMachinesPlay ||
11494              gameMode == IcsPlayingWhite ||
11495              gameMode == IcsPlayingBlack ||
11496              gameMode == BeginningOfGame)) {
11497             SendToProgram("force\n", &first);
11498             if (first.usePing) {
11499               char buf[MSG_SIZ];
11500               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11501               SendToProgram(buf, &first);
11502             }
11503         }
11504     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11505         /* Kill off first chess program */
11506         if (first.isr != NULL)
11507           RemoveInputSource(first.isr);
11508         first.isr = NULL;
11509
11510         if (first.pr != NoProc) {
11511             ExitAnalyzeMode();
11512             DoSleep( appData.delayBeforeQuit );
11513             SendToProgram("quit\n", &first);
11514             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11515             first.reload = TRUE;
11516         }
11517         first.pr = NoProc;
11518     }
11519     if (second.reuse) {
11520         /* Put second chess program into idle state */
11521         if (second.pr != NoProc &&
11522             gameMode == TwoMachinesPlay) {
11523             SendToProgram("force\n", &second);
11524             if (second.usePing) {
11525               char buf[MSG_SIZ];
11526               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11527               SendToProgram(buf, &second);
11528             }
11529         }
11530     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11531         /* Kill off second chess program */
11532         if (second.isr != NULL)
11533           RemoveInputSource(second.isr);
11534         second.isr = NULL;
11535
11536         if (second.pr != NoProc) {
11537             DoSleep( appData.delayBeforeQuit );
11538             SendToProgram("quit\n", &second);
11539             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11540             second.reload = TRUE;
11541         }
11542         second.pr = NoProc;
11543     }
11544
11545     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11546         char resChar = '=';
11547         switch (result) {
11548         case WhiteWins:
11549           resChar = '+';
11550           if (first.twoMachinesColor[0] == 'w') {
11551             first.matchWins++;
11552           } else {
11553             second.matchWins++;
11554           }
11555           break;
11556         case BlackWins:
11557           resChar = '-';
11558           if (first.twoMachinesColor[0] == 'b') {
11559             first.matchWins++;
11560           } else {
11561             second.matchWins++;
11562           }
11563           break;
11564         case GameUnfinished:
11565           resChar = ' ';
11566         default:
11567           break;
11568         }
11569
11570         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11571         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11572             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11573             ReserveGame(nextGame, resChar); // sets nextGame
11574             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11575             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11576         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11577
11578         if (nextGame <= appData.matchGames && !abortMatch) {
11579             gameMode = nextGameMode;
11580             matchGame = nextGame; // this will be overruled in tourney mode!
11581             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11582             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11583             endingGame = 0; /* [HGM] crash */
11584             return;
11585         } else {
11586             gameMode = nextGameMode;
11587             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11588                      first.tidy, second.tidy,
11589                      first.matchWins, second.matchWins,
11590                      appData.matchGames - (first.matchWins + second.matchWins));
11591             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11592             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11593             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11594             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11595                 first.twoMachinesColor = "black\n";
11596                 second.twoMachinesColor = "white\n";
11597             } else {
11598                 first.twoMachinesColor = "white\n";
11599                 second.twoMachinesColor = "black\n";
11600             }
11601         }
11602     }
11603     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11604         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11605       ExitAnalyzeMode();
11606     gameMode = nextGameMode;
11607     ModeHighlight();
11608     endingGame = 0;  /* [HGM] crash */
11609     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11610         if(matchMode == TRUE) { // match through command line: exit with or without popup
11611             if(ranking) {
11612                 ToNrEvent(forwardMostMove);
11613                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11614                 else ExitEvent(0);
11615             } else DisplayFatalError(buf, 0, 0);
11616         } else { // match through menu; just stop, with or without popup
11617             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11618             ModeHighlight();
11619             if(ranking){
11620                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11621             } else DisplayNote(buf);
11622       }
11623       if(ranking) free(ranking);
11624     }
11625 }
11626
11627 /* Assumes program was just initialized (initString sent).
11628    Leaves program in force mode. */
11629 void
11630 FeedMovesToProgram (ChessProgramState *cps, int upto)
11631 {
11632     int i;
11633
11634     if (appData.debugMode)
11635       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11636               startedFromSetupPosition ? "position and " : "",
11637               backwardMostMove, upto, cps->which);
11638     if(currentlyInitializedVariant != gameInfo.variant) {
11639       char buf[MSG_SIZ];
11640         // [HGM] variantswitch: make engine aware of new variant
11641         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11642                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11643                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11644         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11645         SendToProgram(buf, cps);
11646         currentlyInitializedVariant = gameInfo.variant;
11647     }
11648     SendToProgram("force\n", cps);
11649     if (startedFromSetupPosition) {
11650         SendBoard(cps, backwardMostMove);
11651     if (appData.debugMode) {
11652         fprintf(debugFP, "feedMoves\n");
11653     }
11654     }
11655     for (i = backwardMostMove; i < upto; i++) {
11656         SendMoveToProgram(i, cps);
11657     }
11658 }
11659
11660
11661 int
11662 ResurrectChessProgram ()
11663 {
11664      /* The chess program may have exited.
11665         If so, restart it and feed it all the moves made so far. */
11666     static int doInit = 0;
11667
11668     if (appData.noChessProgram) return 1;
11669
11670     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11671         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11672         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11673         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11674     } else {
11675         if (first.pr != NoProc) return 1;
11676         StartChessProgram(&first);
11677     }
11678     InitChessProgram(&first, FALSE);
11679     FeedMovesToProgram(&first, currentMove);
11680
11681     if (!first.sendTime) {
11682         /* can't tell gnuchess what its clock should read,
11683            so we bow to its notion. */
11684         ResetClocks();
11685         timeRemaining[0][currentMove] = whiteTimeRemaining;
11686         timeRemaining[1][currentMove] = blackTimeRemaining;
11687     }
11688
11689     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11690                 appData.icsEngineAnalyze) && first.analysisSupport) {
11691       SendToProgram("analyze\n", &first);
11692       first.analyzing = TRUE;
11693     }
11694     return 1;
11695 }
11696
11697 /*
11698  * Button procedures
11699  */
11700 void
11701 Reset (int redraw, int init)
11702 {
11703     int i;
11704
11705     if (appData.debugMode) {
11706         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11707                 redraw, init, gameMode);
11708     }
11709     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11710     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11711     CleanupTail(); // [HGM] vari: delete any stored variations
11712     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11713     pausing = pauseExamInvalid = FALSE;
11714     startedFromSetupPosition = blackPlaysFirst = FALSE;
11715     firstMove = TRUE;
11716     whiteFlag = blackFlag = FALSE;
11717     userOfferedDraw = FALSE;
11718     hintRequested = bookRequested = FALSE;
11719     first.maybeThinking = FALSE;
11720     second.maybeThinking = FALSE;
11721     first.bookSuspend = FALSE; // [HGM] book
11722     second.bookSuspend = FALSE;
11723     thinkOutput[0] = NULLCHAR;
11724     lastHint[0] = NULLCHAR;
11725     ClearGameInfo(&gameInfo);
11726     gameInfo.variant = StringToVariant(appData.variant);
11727     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11728     ics_user_moved = ics_clock_paused = FALSE;
11729     ics_getting_history = H_FALSE;
11730     ics_gamenum = -1;
11731     white_holding[0] = black_holding[0] = NULLCHAR;
11732     ClearProgramStats();
11733     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11734
11735     ResetFrontEnd();
11736     ClearHighlights();
11737     flipView = appData.flipView;
11738     ClearPremoveHighlights();
11739     gotPremove = FALSE;
11740     alarmSounded = FALSE;
11741     killX = killY = -1; // [HGM] lion
11742
11743     GameEnds(EndOfFile, NULL, GE_PLAYER);
11744     if(appData.serverMovesName != NULL) {
11745         /* [HGM] prepare to make moves file for broadcasting */
11746         clock_t t = clock();
11747         if(serverMoves != NULL) fclose(serverMoves);
11748         serverMoves = fopen(appData.serverMovesName, "r");
11749         if(serverMoves != NULL) {
11750             fclose(serverMoves);
11751             /* delay 15 sec before overwriting, so all clients can see end */
11752             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11753         }
11754         serverMoves = fopen(appData.serverMovesName, "w");
11755     }
11756
11757     ExitAnalyzeMode();
11758     gameMode = BeginningOfGame;
11759     ModeHighlight();
11760     if(appData.icsActive) gameInfo.variant = VariantNormal;
11761     currentMove = forwardMostMove = backwardMostMove = 0;
11762     MarkTargetSquares(1);
11763     InitPosition(redraw);
11764     for (i = 0; i < MAX_MOVES; i++) {
11765         if (commentList[i] != NULL) {
11766             free(commentList[i]);
11767             commentList[i] = NULL;
11768         }
11769     }
11770     ResetClocks();
11771     timeRemaining[0][0] = whiteTimeRemaining;
11772     timeRemaining[1][0] = blackTimeRemaining;
11773
11774     if (first.pr == NoProc) {
11775         StartChessProgram(&first);
11776     }
11777     if (init) {
11778             InitChessProgram(&first, startedFromSetupPosition);
11779     }
11780     DisplayTitle("");
11781     DisplayMessage("", "");
11782     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11783     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11784     ClearMap();        // [HGM] exclude: invalidate map
11785 }
11786
11787 void
11788 AutoPlayGameLoop ()
11789 {
11790     for (;;) {
11791         if (!AutoPlayOneMove())
11792           return;
11793         if (matchMode || appData.timeDelay == 0)
11794           continue;
11795         if (appData.timeDelay < 0)
11796           return;
11797         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11798         break;
11799     }
11800 }
11801
11802 void
11803 AnalyzeNextGame()
11804 {
11805     ReloadGame(1); // next game
11806 }
11807
11808 int
11809 AutoPlayOneMove ()
11810 {
11811     int fromX, fromY, toX, toY;
11812
11813     if (appData.debugMode) {
11814       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11815     }
11816
11817     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11818       return FALSE;
11819
11820     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11821       pvInfoList[currentMove].depth = programStats.depth;
11822       pvInfoList[currentMove].score = programStats.score;
11823       pvInfoList[currentMove].time  = 0;
11824       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11825       else { // append analysis of final position as comment
11826         char buf[MSG_SIZ];
11827         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11828         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11829       }
11830       programStats.depth = 0;
11831     }
11832
11833     if (currentMove >= forwardMostMove) {
11834       if(gameMode == AnalyzeFile) {
11835           if(appData.loadGameIndex == -1) {
11836             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11837           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11838           } else {
11839           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11840         }
11841       }
11842 //      gameMode = EndOfGame;
11843 //      ModeHighlight();
11844
11845       /* [AS] Clear current move marker at the end of a game */
11846       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11847
11848       return FALSE;
11849     }
11850
11851     toX = moveList[currentMove][2] - AAA;
11852     toY = moveList[currentMove][3] - ONE;
11853
11854     if (moveList[currentMove][1] == '@') {
11855         if (appData.highlightLastMove) {
11856             SetHighlights(-1, -1, toX, toY);
11857         }
11858     } else {
11859         int viaX = moveList[currentMove][5] - AAA;
11860         int viaY = moveList[currentMove][6] - ONE;
11861         fromX = moveList[currentMove][0] - AAA;
11862         fromY = moveList[currentMove][1] - ONE;
11863
11864         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11865
11866         if(moveList[currentMove][4] == ';') { // multi-leg
11867             ChessSquare piece = boards[currentMove][viaY][viaX];
11868             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11869             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11870             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11871             boards[currentMove][viaY][viaX] = piece;
11872         } else
11873         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11874
11875         if (appData.highlightLastMove) {
11876             SetHighlights(fromX, fromY, toX, toY);
11877         }
11878     }
11879     DisplayMove(currentMove);
11880     SendMoveToProgram(currentMove++, &first);
11881     DisplayBothClocks();
11882     DrawPosition(FALSE, boards[currentMove]);
11883     // [HGM] PV info: always display, routine tests if empty
11884     DisplayComment(currentMove - 1, commentList[currentMove]);
11885     return TRUE;
11886 }
11887
11888
11889 int
11890 LoadGameOneMove (ChessMove readAhead)
11891 {
11892     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11893     char promoChar = NULLCHAR;
11894     ChessMove moveType;
11895     char move[MSG_SIZ];
11896     char *p, *q;
11897
11898     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11899         gameMode != AnalyzeMode && gameMode != Training) {
11900         gameFileFP = NULL;
11901         return FALSE;
11902     }
11903
11904     yyboardindex = forwardMostMove;
11905     if (readAhead != EndOfFile) {
11906       moveType = readAhead;
11907     } else {
11908       if (gameFileFP == NULL)
11909           return FALSE;
11910       moveType = (ChessMove) Myylex();
11911     }
11912
11913     done = FALSE;
11914     switch (moveType) {
11915       case Comment:
11916         if (appData.debugMode)
11917           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11918         p = yy_text;
11919
11920         /* append the comment but don't display it */
11921         AppendComment(currentMove, p, FALSE);
11922         return TRUE;
11923
11924       case WhiteCapturesEnPassant:
11925       case BlackCapturesEnPassant:
11926       case WhitePromotion:
11927       case BlackPromotion:
11928       case WhiteNonPromotion:
11929       case BlackNonPromotion:
11930       case NormalMove:
11931       case FirstLeg:
11932       case WhiteKingSideCastle:
11933       case WhiteQueenSideCastle:
11934       case BlackKingSideCastle:
11935       case BlackQueenSideCastle:
11936       case WhiteKingSideCastleWild:
11937       case WhiteQueenSideCastleWild:
11938       case BlackKingSideCastleWild:
11939       case BlackQueenSideCastleWild:
11940       /* PUSH Fabien */
11941       case WhiteHSideCastleFR:
11942       case WhiteASideCastleFR:
11943       case BlackHSideCastleFR:
11944       case BlackASideCastleFR:
11945       /* POP Fabien */
11946         if (appData.debugMode)
11947           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11948         fromX = currentMoveString[0] - AAA;
11949         fromY = currentMoveString[1] - ONE;
11950         toX = currentMoveString[2] - AAA;
11951         toY = currentMoveString[3] - ONE;
11952         promoChar = currentMoveString[4];
11953         if(promoChar == ';') promoChar = NULLCHAR;
11954         break;
11955
11956       case WhiteDrop:
11957       case BlackDrop:
11958         if (appData.debugMode)
11959           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11960         fromX = moveType == WhiteDrop ?
11961           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11962         (int) CharToPiece(ToLower(currentMoveString[0]));
11963         fromY = DROP_RANK;
11964         toX = currentMoveString[2] - AAA;
11965         toY = currentMoveString[3] - ONE;
11966         break;
11967
11968       case WhiteWins:
11969       case BlackWins:
11970       case GameIsDrawn:
11971       case GameUnfinished:
11972         if (appData.debugMode)
11973           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11974         p = strchr(yy_text, '{');
11975         if (p == NULL) p = strchr(yy_text, '(');
11976         if (p == NULL) {
11977             p = yy_text;
11978             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11979         } else {
11980             q = strchr(p, *p == '{' ? '}' : ')');
11981             if (q != NULL) *q = NULLCHAR;
11982             p++;
11983         }
11984         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11985         GameEnds(moveType, p, GE_FILE);
11986         done = TRUE;
11987         if (cmailMsgLoaded) {
11988             ClearHighlights();
11989             flipView = WhiteOnMove(currentMove);
11990             if (moveType == GameUnfinished) flipView = !flipView;
11991             if (appData.debugMode)
11992               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11993         }
11994         break;
11995
11996       case EndOfFile:
11997         if (appData.debugMode)
11998           fprintf(debugFP, "Parser hit end of file\n");
11999         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12000           case MT_NONE:
12001           case MT_CHECK:
12002             break;
12003           case MT_CHECKMATE:
12004           case MT_STAINMATE:
12005             if (WhiteOnMove(currentMove)) {
12006                 GameEnds(BlackWins, "Black mates", GE_FILE);
12007             } else {
12008                 GameEnds(WhiteWins, "White mates", GE_FILE);
12009             }
12010             break;
12011           case MT_STALEMATE:
12012             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12013             break;
12014         }
12015         done = TRUE;
12016         break;
12017
12018       case MoveNumberOne:
12019         if (lastLoadGameStart == GNUChessGame) {
12020             /* GNUChessGames have numbers, but they aren't move numbers */
12021             if (appData.debugMode)
12022               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12023                       yy_text, (int) moveType);
12024             return LoadGameOneMove(EndOfFile); /* tail recursion */
12025         }
12026         /* else fall thru */
12027
12028       case XBoardGame:
12029       case GNUChessGame:
12030       case PGNTag:
12031         /* Reached start of next game in file */
12032         if (appData.debugMode)
12033           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12034         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12035           case MT_NONE:
12036           case MT_CHECK:
12037             break;
12038           case MT_CHECKMATE:
12039           case MT_STAINMATE:
12040             if (WhiteOnMove(currentMove)) {
12041                 GameEnds(BlackWins, "Black mates", GE_FILE);
12042             } else {
12043                 GameEnds(WhiteWins, "White mates", GE_FILE);
12044             }
12045             break;
12046           case MT_STALEMATE:
12047             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12048             break;
12049         }
12050         done = TRUE;
12051         break;
12052
12053       case PositionDiagram:     /* should not happen; ignore */
12054       case ElapsedTime:         /* ignore */
12055       case NAG:                 /* ignore */
12056         if (appData.debugMode)
12057           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12058                   yy_text, (int) moveType);
12059         return LoadGameOneMove(EndOfFile); /* tail recursion */
12060
12061       case IllegalMove:
12062         if (appData.testLegality) {
12063             if (appData.debugMode)
12064               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12065             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12066                     (forwardMostMove / 2) + 1,
12067                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12068             DisplayError(move, 0);
12069             done = TRUE;
12070         } else {
12071             if (appData.debugMode)
12072               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12073                       yy_text, currentMoveString);
12074             fromX = currentMoveString[0] - AAA;
12075             fromY = currentMoveString[1] - ONE;
12076             toX = currentMoveString[2] - AAA;
12077             toY = currentMoveString[3] - ONE;
12078             promoChar = currentMoveString[4];
12079         }
12080         break;
12081
12082       case AmbiguousMove:
12083         if (appData.debugMode)
12084           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12085         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12086                 (forwardMostMove / 2) + 1,
12087                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12088         DisplayError(move, 0);
12089         done = TRUE;
12090         break;
12091
12092       default:
12093       case ImpossibleMove:
12094         if (appData.debugMode)
12095           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12096         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12097                 (forwardMostMove / 2) + 1,
12098                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12099         DisplayError(move, 0);
12100         done = TRUE;
12101         break;
12102     }
12103
12104     if (done) {
12105         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12106             DrawPosition(FALSE, boards[currentMove]);
12107             DisplayBothClocks();
12108             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12109               DisplayComment(currentMove - 1, commentList[currentMove]);
12110         }
12111         (void) StopLoadGameTimer();
12112         gameFileFP = NULL;
12113         cmailOldMove = forwardMostMove;
12114         return FALSE;
12115     } else {
12116         /* currentMoveString is set as a side-effect of yylex */
12117
12118         thinkOutput[0] = NULLCHAR;
12119         MakeMove(fromX, fromY, toX, toY, promoChar);
12120         killX = killY = -1; // [HGM] lion: used up
12121         currentMove = forwardMostMove;
12122         return TRUE;
12123     }
12124 }
12125
12126 /* Load the nth game from the given file */
12127 int
12128 LoadGameFromFile (char *filename, int n, char *title, int useList)
12129 {
12130     FILE *f;
12131     char buf[MSG_SIZ];
12132
12133     if (strcmp(filename, "-") == 0) {
12134         f = stdin;
12135         title = "stdin";
12136     } else {
12137         f = fopen(filename, "rb");
12138         if (f == NULL) {
12139           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12140             DisplayError(buf, errno);
12141             return FALSE;
12142         }
12143     }
12144     if (fseek(f, 0, 0) == -1) {
12145         /* f is not seekable; probably a pipe */
12146         useList = FALSE;
12147     }
12148     if (useList && n == 0) {
12149         int error = GameListBuild(f);
12150         if (error) {
12151             DisplayError(_("Cannot build game list"), error);
12152         } else if (!ListEmpty(&gameList) &&
12153                    ((ListGame *) gameList.tailPred)->number > 1) {
12154             GameListPopUp(f, title);
12155             return TRUE;
12156         }
12157         GameListDestroy();
12158         n = 1;
12159     }
12160     if (n == 0) n = 1;
12161     return LoadGame(f, n, title, FALSE);
12162 }
12163
12164
12165 void
12166 MakeRegisteredMove ()
12167 {
12168     int fromX, fromY, toX, toY;
12169     char promoChar;
12170     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12171         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12172           case CMAIL_MOVE:
12173           case CMAIL_DRAW:
12174             if (appData.debugMode)
12175               fprintf(debugFP, "Restoring %s for game %d\n",
12176                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12177
12178             thinkOutput[0] = NULLCHAR;
12179             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12180             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12181             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12182             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12183             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12184             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12185             MakeMove(fromX, fromY, toX, toY, promoChar);
12186             ShowMove(fromX, fromY, toX, toY);
12187
12188             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12189               case MT_NONE:
12190               case MT_CHECK:
12191                 break;
12192
12193               case MT_CHECKMATE:
12194               case MT_STAINMATE:
12195                 if (WhiteOnMove(currentMove)) {
12196                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12197                 } else {
12198                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12199                 }
12200                 break;
12201
12202               case MT_STALEMATE:
12203                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12204                 break;
12205             }
12206
12207             break;
12208
12209           case CMAIL_RESIGN:
12210             if (WhiteOnMove(currentMove)) {
12211                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12212             } else {
12213                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12214             }
12215             break;
12216
12217           case CMAIL_ACCEPT:
12218             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12219             break;
12220
12221           default:
12222             break;
12223         }
12224     }
12225
12226     return;
12227 }
12228
12229 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12230 int
12231 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12232 {
12233     int retVal;
12234
12235     if (gameNumber > nCmailGames) {
12236         DisplayError(_("No more games in this message"), 0);
12237         return FALSE;
12238     }
12239     if (f == lastLoadGameFP) {
12240         int offset = gameNumber - lastLoadGameNumber;
12241         if (offset == 0) {
12242             cmailMsg[0] = NULLCHAR;
12243             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12244                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12245                 nCmailMovesRegistered--;
12246             }
12247             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12248             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12249                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12250             }
12251         } else {
12252             if (! RegisterMove()) return FALSE;
12253         }
12254     }
12255
12256     retVal = LoadGame(f, gameNumber, title, useList);
12257
12258     /* Make move registered during previous look at this game, if any */
12259     MakeRegisteredMove();
12260
12261     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12262         commentList[currentMove]
12263           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12264         DisplayComment(currentMove - 1, commentList[currentMove]);
12265     }
12266
12267     return retVal;
12268 }
12269
12270 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12271 int
12272 ReloadGame (int offset)
12273 {
12274     int gameNumber = lastLoadGameNumber + offset;
12275     if (lastLoadGameFP == NULL) {
12276         DisplayError(_("No game has been loaded yet"), 0);
12277         return FALSE;
12278     }
12279     if (gameNumber <= 0) {
12280         DisplayError(_("Can't back up any further"), 0);
12281         return FALSE;
12282     }
12283     if (cmailMsgLoaded) {
12284         return CmailLoadGame(lastLoadGameFP, gameNumber,
12285                              lastLoadGameTitle, lastLoadGameUseList);
12286     } else {
12287         return LoadGame(lastLoadGameFP, gameNumber,
12288                         lastLoadGameTitle, lastLoadGameUseList);
12289     }
12290 }
12291
12292 int keys[EmptySquare+1];
12293
12294 int
12295 PositionMatches (Board b1, Board b2)
12296 {
12297     int r, f, sum=0;
12298     switch(appData.searchMode) {
12299         case 1: return CompareWithRights(b1, b2);
12300         case 2:
12301             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12302                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12303             }
12304             return TRUE;
12305         case 3:
12306             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12307               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12308                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12309             }
12310             return sum==0;
12311         case 4:
12312             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12313                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12314             }
12315             return sum==0;
12316     }
12317     return TRUE;
12318 }
12319
12320 #define Q_PROMO  4
12321 #define Q_EP     3
12322 #define Q_BCASTL 2
12323 #define Q_WCASTL 1
12324
12325 int pieceList[256], quickBoard[256];
12326 ChessSquare pieceType[256] = { EmptySquare };
12327 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12328 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12329 int soughtTotal, turn;
12330 Boolean epOK, flipSearch;
12331
12332 typedef struct {
12333     unsigned char piece, to;
12334 } Move;
12335
12336 #define DSIZE (250000)
12337
12338 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12339 Move *moveDatabase = initialSpace;
12340 unsigned int movePtr, dataSize = DSIZE;
12341
12342 int
12343 MakePieceList (Board board, int *counts)
12344 {
12345     int r, f, n=Q_PROMO, total=0;
12346     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12347     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12348         int sq = f + (r<<4);
12349         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12350             quickBoard[sq] = ++n;
12351             pieceList[n] = sq;
12352             pieceType[n] = board[r][f];
12353             counts[board[r][f]]++;
12354             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12355             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12356             total++;
12357         }
12358     }
12359     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12360     return total;
12361 }
12362
12363 void
12364 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12365 {
12366     int sq = fromX + (fromY<<4);
12367     int piece = quickBoard[sq], rook;
12368     quickBoard[sq] = 0;
12369     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12370     if(piece == pieceList[1] && fromY == toY) {
12371       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12372         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12373         moveDatabase[movePtr++].piece = Q_WCASTL;
12374         quickBoard[sq] = piece;
12375         piece = quickBoard[from]; quickBoard[from] = 0;
12376         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12377       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12378         quickBoard[sq] = 0; // remove Rook
12379         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12380         moveDatabase[movePtr++].piece = Q_WCASTL;
12381         quickBoard[sq] = pieceList[1]; // put King
12382         piece = rook;
12383         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12384       }
12385     } else
12386     if(piece == pieceList[2] && fromY == toY) {
12387       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12388         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12389         moveDatabase[movePtr++].piece = Q_BCASTL;
12390         quickBoard[sq] = piece;
12391         piece = quickBoard[from]; quickBoard[from] = 0;
12392         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12393       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12394         quickBoard[sq] = 0; // remove Rook
12395         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12396         moveDatabase[movePtr++].piece = Q_BCASTL;
12397         quickBoard[sq] = pieceList[2]; // put King
12398         piece = rook;
12399         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12400       }
12401     } else
12402     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12403         quickBoard[(fromY<<4)+toX] = 0;
12404         moveDatabase[movePtr].piece = Q_EP;
12405         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12406         moveDatabase[movePtr].to = sq;
12407     } else
12408     if(promoPiece != pieceType[piece]) {
12409         moveDatabase[movePtr++].piece = Q_PROMO;
12410         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12411     }
12412     moveDatabase[movePtr].piece = piece;
12413     quickBoard[sq] = piece;
12414     movePtr++;
12415 }
12416
12417 int
12418 PackGame (Board board)
12419 {
12420     Move *newSpace = NULL;
12421     moveDatabase[movePtr].piece = 0; // terminate previous game
12422     if(movePtr > dataSize) {
12423         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12424         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12425         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12426         if(newSpace) {
12427             int i;
12428             Move *p = moveDatabase, *q = newSpace;
12429             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12430             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12431             moveDatabase = newSpace;
12432         } else { // calloc failed, we must be out of memory. Too bad...
12433             dataSize = 0; // prevent calloc events for all subsequent games
12434             return 0;     // and signal this one isn't cached
12435         }
12436     }
12437     movePtr++;
12438     MakePieceList(board, counts);
12439     return movePtr;
12440 }
12441
12442 int
12443 QuickCompare (Board board, int *minCounts, int *maxCounts)
12444 {   // compare according to search mode
12445     int r, f;
12446     switch(appData.searchMode)
12447     {
12448       case 1: // exact position match
12449         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12450         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12451             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12452         }
12453         break;
12454       case 2: // can have extra material on empty squares
12455         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12456             if(board[r][f] == EmptySquare) continue;
12457             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12458         }
12459         break;
12460       case 3: // material with exact Pawn structure
12461         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12462             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12463             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12464         } // fall through to material comparison
12465       case 4: // exact material
12466         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12467         break;
12468       case 6: // material range with given imbalance
12469         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12470         // fall through to range comparison
12471       case 5: // material range
12472         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12473     }
12474     return TRUE;
12475 }
12476
12477 int
12478 QuickScan (Board board, Move *move)
12479 {   // reconstruct game,and compare all positions in it
12480     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12481     do {
12482         int piece = move->piece;
12483         int to = move->to, from = pieceList[piece];
12484         if(found < 0) { // if already found just scan to game end for final piece count
12485           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12486            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12487            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12488                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12489             ) {
12490             static int lastCounts[EmptySquare+1];
12491             int i;
12492             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12493             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12494           } else stretch = 0;
12495           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12496           if(found >= 0 && !appData.minPieces) return found;
12497         }
12498         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12499           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12500           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12501             piece = (++move)->piece;
12502             from = pieceList[piece];
12503             counts[pieceType[piece]]--;
12504             pieceType[piece] = (ChessSquare) move->to;
12505             counts[move->to]++;
12506           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12507             counts[pieceType[quickBoard[to]]]--;
12508             quickBoard[to] = 0; total--;
12509             move++;
12510             continue;
12511           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12512             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12513             from  = pieceList[piece]; // so this must be King
12514             quickBoard[from] = 0;
12515             pieceList[piece] = to;
12516             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12517             quickBoard[from] = 0; // rook
12518             quickBoard[to] = piece;
12519             to = move->to; piece = move->piece;
12520             goto aftercastle;
12521           }
12522         }
12523         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12524         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12525         quickBoard[from] = 0;
12526       aftercastle:
12527         quickBoard[to] = piece;
12528         pieceList[piece] = to;
12529         cnt++; turn ^= 3;
12530         move++;
12531     } while(1);
12532 }
12533
12534 void
12535 InitSearch ()
12536 {
12537     int r, f;
12538     flipSearch = FALSE;
12539     CopyBoard(soughtBoard, boards[currentMove]);
12540     soughtTotal = MakePieceList(soughtBoard, maxSought);
12541     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12542     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12543     CopyBoard(reverseBoard, boards[currentMove]);
12544     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12545         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12546         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12547         reverseBoard[r][f] = piece;
12548     }
12549     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12550     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12551     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12552                  || (boards[currentMove][CASTLING][2] == NoRights ||
12553                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12554                  && (boards[currentMove][CASTLING][5] == NoRights ||
12555                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12556       ) {
12557         flipSearch = TRUE;
12558         CopyBoard(flipBoard, soughtBoard);
12559         CopyBoard(rotateBoard, reverseBoard);
12560         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12561             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12562             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12563         }
12564     }
12565     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12566     if(appData.searchMode >= 5) {
12567         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12568         MakePieceList(soughtBoard, minSought);
12569         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12570     }
12571     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12572         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12573 }
12574
12575 GameInfo dummyInfo;
12576 static int creatingBook;
12577
12578 int
12579 GameContainsPosition (FILE *f, ListGame *lg)
12580 {
12581     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12582     int fromX, fromY, toX, toY;
12583     char promoChar;
12584     static int initDone=FALSE;
12585
12586     // weed out games based on numerical tag comparison
12587     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12588     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12589     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12590     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12591     if(!initDone) {
12592         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12593         initDone = TRUE;
12594     }
12595     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12596     else CopyBoard(boards[scratch], initialPosition); // default start position
12597     if(lg->moves) {
12598         turn = btm + 1;
12599         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12600         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12601     }
12602     if(btm) plyNr++;
12603     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12604     fseek(f, lg->offset, 0);
12605     yynewfile(f);
12606     while(1) {
12607         yyboardindex = scratch;
12608         quickFlag = plyNr+1;
12609         next = Myylex();
12610         quickFlag = 0;
12611         switch(next) {
12612             case PGNTag:
12613                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12614             default:
12615                 continue;
12616
12617             case XBoardGame:
12618             case GNUChessGame:
12619                 if(plyNr) return -1; // after we have seen moves, this is for new game
12620               continue;
12621
12622             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12623             case ImpossibleMove:
12624             case WhiteWins: // game ends here with these four
12625             case BlackWins:
12626             case GameIsDrawn:
12627             case GameUnfinished:
12628                 return -1;
12629
12630             case IllegalMove:
12631                 if(appData.testLegality) return -1;
12632             case WhiteCapturesEnPassant:
12633             case BlackCapturesEnPassant:
12634             case WhitePromotion:
12635             case BlackPromotion:
12636             case WhiteNonPromotion:
12637             case BlackNonPromotion:
12638             case NormalMove:
12639             case FirstLeg:
12640             case WhiteKingSideCastle:
12641             case WhiteQueenSideCastle:
12642             case BlackKingSideCastle:
12643             case BlackQueenSideCastle:
12644             case WhiteKingSideCastleWild:
12645             case WhiteQueenSideCastleWild:
12646             case BlackKingSideCastleWild:
12647             case BlackQueenSideCastleWild:
12648             case WhiteHSideCastleFR:
12649             case WhiteASideCastleFR:
12650             case BlackHSideCastleFR:
12651             case BlackASideCastleFR:
12652                 fromX = currentMoveString[0] - AAA;
12653                 fromY = currentMoveString[1] - ONE;
12654                 toX = currentMoveString[2] - AAA;
12655                 toY = currentMoveString[3] - ONE;
12656                 promoChar = currentMoveString[4];
12657                 break;
12658             case WhiteDrop:
12659             case BlackDrop:
12660                 fromX = next == WhiteDrop ?
12661                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12662                   (int) CharToPiece(ToLower(currentMoveString[0]));
12663                 fromY = DROP_RANK;
12664                 toX = currentMoveString[2] - AAA;
12665                 toY = currentMoveString[3] - ONE;
12666                 promoChar = 0;
12667                 break;
12668         }
12669         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12670         plyNr++;
12671         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12672         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12673         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12674         if(appData.findMirror) {
12675             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12676             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12677         }
12678     }
12679 }
12680
12681 /* Load the nth game from open file f */
12682 int
12683 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12684 {
12685     ChessMove cm;
12686     char buf[MSG_SIZ];
12687     int gn = gameNumber;
12688     ListGame *lg = NULL;
12689     int numPGNTags = 0;
12690     int err, pos = -1;
12691     GameMode oldGameMode;
12692     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12693
12694     if (appData.debugMode)
12695         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12696
12697     if (gameMode == Training )
12698         SetTrainingModeOff();
12699
12700     oldGameMode = gameMode;
12701     if (gameMode != BeginningOfGame) {
12702       Reset(FALSE, TRUE);
12703     }
12704     killX = killY = -1; // [HGM] lion: in case we did not Reset
12705
12706     gameFileFP = f;
12707     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12708         fclose(lastLoadGameFP);
12709     }
12710
12711     if (useList) {
12712         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12713
12714         if (lg) {
12715             fseek(f, lg->offset, 0);
12716             GameListHighlight(gameNumber);
12717             pos = lg->position;
12718             gn = 1;
12719         }
12720         else {
12721             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12722               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12723             else
12724             DisplayError(_("Game number out of range"), 0);
12725             return FALSE;
12726         }
12727     } else {
12728         GameListDestroy();
12729         if (fseek(f, 0, 0) == -1) {
12730             if (f == lastLoadGameFP ?
12731                 gameNumber == lastLoadGameNumber + 1 :
12732                 gameNumber == 1) {
12733                 gn = 1;
12734             } else {
12735                 DisplayError(_("Can't seek on game file"), 0);
12736                 return FALSE;
12737             }
12738         }
12739     }
12740     lastLoadGameFP = f;
12741     lastLoadGameNumber = gameNumber;
12742     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12743     lastLoadGameUseList = useList;
12744
12745     yynewfile(f);
12746
12747     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12748       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12749                 lg->gameInfo.black);
12750             DisplayTitle(buf);
12751     } else if (*title != NULLCHAR) {
12752         if (gameNumber > 1) {
12753           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12754             DisplayTitle(buf);
12755         } else {
12756             DisplayTitle(title);
12757         }
12758     }
12759
12760     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12761         gameMode = PlayFromGameFile;
12762         ModeHighlight();
12763     }
12764
12765     currentMove = forwardMostMove = backwardMostMove = 0;
12766     CopyBoard(boards[0], initialPosition);
12767     StopClocks();
12768
12769     /*
12770      * Skip the first gn-1 games in the file.
12771      * Also skip over anything that precedes an identifiable
12772      * start of game marker, to avoid being confused by
12773      * garbage at the start of the file.  Currently
12774      * recognized start of game markers are the move number "1",
12775      * the pattern "gnuchess .* game", the pattern
12776      * "^[#;%] [^ ]* game file", and a PGN tag block.
12777      * A game that starts with one of the latter two patterns
12778      * will also have a move number 1, possibly
12779      * following a position diagram.
12780      * 5-4-02: Let's try being more lenient and allowing a game to
12781      * start with an unnumbered move.  Does that break anything?
12782      */
12783     cm = lastLoadGameStart = EndOfFile;
12784     while (gn > 0) {
12785         yyboardindex = forwardMostMove;
12786         cm = (ChessMove) Myylex();
12787         switch (cm) {
12788           case EndOfFile:
12789             if (cmailMsgLoaded) {
12790                 nCmailGames = CMAIL_MAX_GAMES - gn;
12791             } else {
12792                 Reset(TRUE, TRUE);
12793                 DisplayError(_("Game not found in file"), 0);
12794             }
12795             return FALSE;
12796
12797           case GNUChessGame:
12798           case XBoardGame:
12799             gn--;
12800             lastLoadGameStart = cm;
12801             break;
12802
12803           case MoveNumberOne:
12804             switch (lastLoadGameStart) {
12805               case GNUChessGame:
12806               case XBoardGame:
12807               case PGNTag:
12808                 break;
12809               case MoveNumberOne:
12810               case EndOfFile:
12811                 gn--;           /* count this game */
12812                 lastLoadGameStart = cm;
12813                 break;
12814               default:
12815                 /* impossible */
12816                 break;
12817             }
12818             break;
12819
12820           case PGNTag:
12821             switch (lastLoadGameStart) {
12822               case GNUChessGame:
12823               case PGNTag:
12824               case MoveNumberOne:
12825               case EndOfFile:
12826                 gn--;           /* count this game */
12827                 lastLoadGameStart = cm;
12828                 break;
12829               case XBoardGame:
12830                 lastLoadGameStart = cm; /* game counted already */
12831                 break;
12832               default:
12833                 /* impossible */
12834                 break;
12835             }
12836             if (gn > 0) {
12837                 do {
12838                     yyboardindex = forwardMostMove;
12839                     cm = (ChessMove) Myylex();
12840                 } while (cm == PGNTag || cm == Comment);
12841             }
12842             break;
12843
12844           case WhiteWins:
12845           case BlackWins:
12846           case GameIsDrawn:
12847             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12848                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12849                     != CMAIL_OLD_RESULT) {
12850                     nCmailResults ++ ;
12851                     cmailResult[  CMAIL_MAX_GAMES
12852                                 - gn - 1] = CMAIL_OLD_RESULT;
12853                 }
12854             }
12855             break;
12856
12857           case NormalMove:
12858           case FirstLeg:
12859             /* Only a NormalMove can be at the start of a game
12860              * without a position diagram. */
12861             if (lastLoadGameStart == EndOfFile ) {
12862               gn--;
12863               lastLoadGameStart = MoveNumberOne;
12864             }
12865             break;
12866
12867           default:
12868             break;
12869         }
12870     }
12871
12872     if (appData.debugMode)
12873       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12874
12875     if (cm == XBoardGame) {
12876         /* Skip any header junk before position diagram and/or move 1 */
12877         for (;;) {
12878             yyboardindex = forwardMostMove;
12879             cm = (ChessMove) Myylex();
12880
12881             if (cm == EndOfFile ||
12882                 cm == GNUChessGame || cm == XBoardGame) {
12883                 /* Empty game; pretend end-of-file and handle later */
12884                 cm = EndOfFile;
12885                 break;
12886             }
12887
12888             if (cm == MoveNumberOne || cm == PositionDiagram ||
12889                 cm == PGNTag || cm == Comment)
12890               break;
12891         }
12892     } else if (cm == GNUChessGame) {
12893         if (gameInfo.event != NULL) {
12894             free(gameInfo.event);
12895         }
12896         gameInfo.event = StrSave(yy_text);
12897     }
12898
12899     startedFromSetupPosition = FALSE;
12900     while (cm == PGNTag) {
12901         if (appData.debugMode)
12902           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12903         err = ParsePGNTag(yy_text, &gameInfo);
12904         if (!err) numPGNTags++;
12905
12906         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12907         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12908             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12909             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12910             InitPosition(TRUE);
12911             oldVariant = gameInfo.variant;
12912             if (appData.debugMode)
12913               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12914         }
12915
12916
12917         if (gameInfo.fen != NULL) {
12918           Board initial_position;
12919           startedFromSetupPosition = TRUE;
12920           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12921             Reset(TRUE, TRUE);
12922             DisplayError(_("Bad FEN position in file"), 0);
12923             return FALSE;
12924           }
12925           CopyBoard(boards[0], initial_position);
12926           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12927             CopyBoard(initialPosition, initial_position);
12928           if (blackPlaysFirst) {
12929             currentMove = forwardMostMove = backwardMostMove = 1;
12930             CopyBoard(boards[1], initial_position);
12931             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12932             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12933             timeRemaining[0][1] = whiteTimeRemaining;
12934             timeRemaining[1][1] = blackTimeRemaining;
12935             if (commentList[0] != NULL) {
12936               commentList[1] = commentList[0];
12937               commentList[0] = NULL;
12938             }
12939           } else {
12940             currentMove = forwardMostMove = backwardMostMove = 0;
12941           }
12942           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12943           {   int i;
12944               initialRulePlies = FENrulePlies;
12945               for( i=0; i< nrCastlingRights; i++ )
12946                   initialRights[i] = initial_position[CASTLING][i];
12947           }
12948           yyboardindex = forwardMostMove;
12949           free(gameInfo.fen);
12950           gameInfo.fen = NULL;
12951         }
12952
12953         yyboardindex = forwardMostMove;
12954         cm = (ChessMove) Myylex();
12955
12956         /* Handle comments interspersed among the tags */
12957         while (cm == Comment) {
12958             char *p;
12959             if (appData.debugMode)
12960               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12961             p = yy_text;
12962             AppendComment(currentMove, p, FALSE);
12963             yyboardindex = forwardMostMove;
12964             cm = (ChessMove) Myylex();
12965         }
12966     }
12967
12968     /* don't rely on existence of Event tag since if game was
12969      * pasted from clipboard the Event tag may not exist
12970      */
12971     if (numPGNTags > 0){
12972         char *tags;
12973         if (gameInfo.variant == VariantNormal) {
12974           VariantClass v = StringToVariant(gameInfo.event);
12975           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12976           if(v < VariantShogi) gameInfo.variant = v;
12977         }
12978         if (!matchMode) {
12979           if( appData.autoDisplayTags ) {
12980             tags = PGNTags(&gameInfo);
12981             TagsPopUp(tags, CmailMsg());
12982             free(tags);
12983           }
12984         }
12985     } else {
12986         /* Make something up, but don't display it now */
12987         SetGameInfo();
12988         TagsPopDown();
12989     }
12990
12991     if (cm == PositionDiagram) {
12992         int i, j;
12993         char *p;
12994         Board initial_position;
12995
12996         if (appData.debugMode)
12997           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12998
12999         if (!startedFromSetupPosition) {
13000             p = yy_text;
13001             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13002               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13003                 switch (*p) {
13004                   case '{':
13005                   case '[':
13006                   case '-':
13007                   case ' ':
13008                   case '\t':
13009                   case '\n':
13010                   case '\r':
13011                     break;
13012                   default:
13013                     initial_position[i][j++] = CharToPiece(*p);
13014                     break;
13015                 }
13016             while (*p == ' ' || *p == '\t' ||
13017                    *p == '\n' || *p == '\r') p++;
13018
13019             if (strncmp(p, "black", strlen("black"))==0)
13020               blackPlaysFirst = TRUE;
13021             else
13022               blackPlaysFirst = FALSE;
13023             startedFromSetupPosition = TRUE;
13024
13025             CopyBoard(boards[0], initial_position);
13026             if (blackPlaysFirst) {
13027                 currentMove = forwardMostMove = backwardMostMove = 1;
13028                 CopyBoard(boards[1], initial_position);
13029                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13030                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13031                 timeRemaining[0][1] = whiteTimeRemaining;
13032                 timeRemaining[1][1] = blackTimeRemaining;
13033                 if (commentList[0] != NULL) {
13034                     commentList[1] = commentList[0];
13035                     commentList[0] = NULL;
13036                 }
13037             } else {
13038                 currentMove = forwardMostMove = backwardMostMove = 0;
13039             }
13040         }
13041         yyboardindex = forwardMostMove;
13042         cm = (ChessMove) Myylex();
13043     }
13044
13045   if(!creatingBook) {
13046     if (first.pr == NoProc) {
13047         StartChessProgram(&first);
13048     }
13049     InitChessProgram(&first, FALSE);
13050     SendToProgram("force\n", &first);
13051     if (startedFromSetupPosition) {
13052         SendBoard(&first, forwardMostMove);
13053     if (appData.debugMode) {
13054         fprintf(debugFP, "Load Game\n");
13055     }
13056         DisplayBothClocks();
13057     }
13058   }
13059
13060     /* [HGM] server: flag to write setup moves in broadcast file as one */
13061     loadFlag = appData.suppressLoadMoves;
13062
13063     while (cm == Comment) {
13064         char *p;
13065         if (appData.debugMode)
13066           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13067         p = yy_text;
13068         AppendComment(currentMove, p, FALSE);
13069         yyboardindex = forwardMostMove;
13070         cm = (ChessMove) Myylex();
13071     }
13072
13073     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13074         cm == WhiteWins || cm == BlackWins ||
13075         cm == GameIsDrawn || cm == GameUnfinished) {
13076         DisplayMessage("", _("No moves in game"));
13077         if (cmailMsgLoaded) {
13078             if (appData.debugMode)
13079               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13080             ClearHighlights();
13081             flipView = FALSE;
13082         }
13083         DrawPosition(FALSE, boards[currentMove]);
13084         DisplayBothClocks();
13085         gameMode = EditGame;
13086         ModeHighlight();
13087         gameFileFP = NULL;
13088         cmailOldMove = 0;
13089         return TRUE;
13090     }
13091
13092     // [HGM] PV info: routine tests if comment empty
13093     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13094         DisplayComment(currentMove - 1, commentList[currentMove]);
13095     }
13096     if (!matchMode && appData.timeDelay != 0)
13097       DrawPosition(FALSE, boards[currentMove]);
13098
13099     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13100       programStats.ok_to_send = 1;
13101     }
13102
13103     /* if the first token after the PGN tags is a move
13104      * and not move number 1, retrieve it from the parser
13105      */
13106     if (cm != MoveNumberOne)
13107         LoadGameOneMove(cm);
13108
13109     /* load the remaining moves from the file */
13110     while (LoadGameOneMove(EndOfFile)) {
13111       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13112       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13113     }
13114
13115     /* rewind to the start of the game */
13116     currentMove = backwardMostMove;
13117
13118     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13119
13120     if (oldGameMode == AnalyzeFile) {
13121       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13122       AnalyzeFileEvent();
13123     } else
13124     if (oldGameMode == AnalyzeMode) {
13125       AnalyzeFileEvent();
13126     }
13127
13128     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13129         long int w, b; // [HGM] adjourn: restore saved clock times
13130         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13131         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13132             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13133             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13134         }
13135     }
13136
13137     if(creatingBook) return TRUE;
13138     if (!matchMode && pos > 0) {
13139         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13140     } else
13141     if (matchMode || appData.timeDelay == 0) {
13142       ToEndEvent();
13143     } else if (appData.timeDelay > 0) {
13144       AutoPlayGameLoop();
13145     }
13146
13147     if (appData.debugMode)
13148         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13149
13150     loadFlag = 0; /* [HGM] true game starts */
13151     return TRUE;
13152 }
13153
13154 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13155 int
13156 ReloadPosition (int offset)
13157 {
13158     int positionNumber = lastLoadPositionNumber + offset;
13159     if (lastLoadPositionFP == NULL) {
13160         DisplayError(_("No position has been loaded yet"), 0);
13161         return FALSE;
13162     }
13163     if (positionNumber <= 0) {
13164         DisplayError(_("Can't back up any further"), 0);
13165         return FALSE;
13166     }
13167     return LoadPosition(lastLoadPositionFP, positionNumber,
13168                         lastLoadPositionTitle);
13169 }
13170
13171 /* Load the nth position from the given file */
13172 int
13173 LoadPositionFromFile (char *filename, int n, char *title)
13174 {
13175     FILE *f;
13176     char buf[MSG_SIZ];
13177
13178     if (strcmp(filename, "-") == 0) {
13179         return LoadPosition(stdin, n, "stdin");
13180     } else {
13181         f = fopen(filename, "rb");
13182         if (f == NULL) {
13183             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13184             DisplayError(buf, errno);
13185             return FALSE;
13186         } else {
13187             return LoadPosition(f, n, title);
13188         }
13189     }
13190 }
13191
13192 /* Load the nth position from the given open file, and close it */
13193 int
13194 LoadPosition (FILE *f, int positionNumber, char *title)
13195 {
13196     char *p, line[MSG_SIZ];
13197     Board initial_position;
13198     int i, j, fenMode, pn;
13199
13200     if (gameMode == Training )
13201         SetTrainingModeOff();
13202
13203     if (gameMode != BeginningOfGame) {
13204         Reset(FALSE, TRUE);
13205     }
13206     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13207         fclose(lastLoadPositionFP);
13208     }
13209     if (positionNumber == 0) positionNumber = 1;
13210     lastLoadPositionFP = f;
13211     lastLoadPositionNumber = positionNumber;
13212     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13213     if (first.pr == NoProc && !appData.noChessProgram) {
13214       StartChessProgram(&first);
13215       InitChessProgram(&first, FALSE);
13216     }
13217     pn = positionNumber;
13218     if (positionNumber < 0) {
13219         /* Negative position number means to seek to that byte offset */
13220         if (fseek(f, -positionNumber, 0) == -1) {
13221             DisplayError(_("Can't seek on position file"), 0);
13222             return FALSE;
13223         };
13224         pn = 1;
13225     } else {
13226         if (fseek(f, 0, 0) == -1) {
13227             if (f == lastLoadPositionFP ?
13228                 positionNumber == lastLoadPositionNumber + 1 :
13229                 positionNumber == 1) {
13230                 pn = 1;
13231             } else {
13232                 DisplayError(_("Can't seek on position file"), 0);
13233                 return FALSE;
13234             }
13235         }
13236     }
13237     /* See if this file is FEN or old-style xboard */
13238     if (fgets(line, MSG_SIZ, f) == NULL) {
13239         DisplayError(_("Position not found in file"), 0);
13240         return FALSE;
13241     }
13242     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13243     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13244
13245     if (pn >= 2) {
13246         if (fenMode || line[0] == '#') pn--;
13247         while (pn > 0) {
13248             /* skip positions before number pn */
13249             if (fgets(line, MSG_SIZ, f) == NULL) {
13250                 Reset(TRUE, TRUE);
13251                 DisplayError(_("Position not found in file"), 0);
13252                 return FALSE;
13253             }
13254             if (fenMode || line[0] == '#') pn--;
13255         }
13256     }
13257
13258     if (fenMode) {
13259         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13260             DisplayError(_("Bad FEN position in file"), 0);
13261             return FALSE;
13262         }
13263     } else {
13264         (void) fgets(line, MSG_SIZ, f);
13265         (void) fgets(line, MSG_SIZ, f);
13266
13267         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13268             (void) fgets(line, MSG_SIZ, f);
13269             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13270                 if (*p == ' ')
13271                   continue;
13272                 initial_position[i][j++] = CharToPiece(*p);
13273             }
13274         }
13275
13276         blackPlaysFirst = FALSE;
13277         if (!feof(f)) {
13278             (void) fgets(line, MSG_SIZ, f);
13279             if (strncmp(line, "black", strlen("black"))==0)
13280               blackPlaysFirst = TRUE;
13281         }
13282     }
13283     startedFromSetupPosition = TRUE;
13284
13285     CopyBoard(boards[0], initial_position);
13286     if (blackPlaysFirst) {
13287         currentMove = forwardMostMove = backwardMostMove = 1;
13288         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13289         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13290         CopyBoard(boards[1], initial_position);
13291         DisplayMessage("", _("Black to play"));
13292     } else {
13293         currentMove = forwardMostMove = backwardMostMove = 0;
13294         DisplayMessage("", _("White to play"));
13295     }
13296     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13297     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13298         SendToProgram("force\n", &first);
13299         SendBoard(&first, forwardMostMove);
13300     }
13301     if (appData.debugMode) {
13302 int i, j;
13303   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13304   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13305         fprintf(debugFP, "Load Position\n");
13306     }
13307
13308     if (positionNumber > 1) {
13309       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13310         DisplayTitle(line);
13311     } else {
13312         DisplayTitle(title);
13313     }
13314     gameMode = EditGame;
13315     ModeHighlight();
13316     ResetClocks();
13317     timeRemaining[0][1] = whiteTimeRemaining;
13318     timeRemaining[1][1] = blackTimeRemaining;
13319     DrawPosition(FALSE, boards[currentMove]);
13320
13321     return TRUE;
13322 }
13323
13324
13325 void
13326 CopyPlayerNameIntoFileName (char **dest, char *src)
13327 {
13328     while (*src != NULLCHAR && *src != ',') {
13329         if (*src == ' ') {
13330             *(*dest)++ = '_';
13331             src++;
13332         } else {
13333             *(*dest)++ = *src++;
13334         }
13335     }
13336 }
13337
13338 char *
13339 DefaultFileName (char *ext)
13340 {
13341     static char def[MSG_SIZ];
13342     char *p;
13343
13344     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13345         p = def;
13346         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13347         *p++ = '-';
13348         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13349         *p++ = '.';
13350         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13351     } else {
13352         def[0] = NULLCHAR;
13353     }
13354     return def;
13355 }
13356
13357 /* Save the current game to the given file */
13358 int
13359 SaveGameToFile (char *filename, int append)
13360 {
13361     FILE *f;
13362     char buf[MSG_SIZ];
13363     int result, i, t,tot=0;
13364
13365     if (strcmp(filename, "-") == 0) {
13366         return SaveGame(stdout, 0, NULL);
13367     } else {
13368         for(i=0; i<10; i++) { // upto 10 tries
13369              f = fopen(filename, append ? "a" : "w");
13370              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13371              if(f || errno != 13) break;
13372              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13373              tot += t;
13374         }
13375         if (f == NULL) {
13376             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13377             DisplayError(buf, errno);
13378             return FALSE;
13379         } else {
13380             safeStrCpy(buf, lastMsg, MSG_SIZ);
13381             DisplayMessage(_("Waiting for access to save file"), "");
13382             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13383             DisplayMessage(_("Saving game"), "");
13384             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13385             result = SaveGame(f, 0, NULL);
13386             DisplayMessage(buf, "");
13387             return result;
13388         }
13389     }
13390 }
13391
13392 char *
13393 SavePart (char *str)
13394 {
13395     static char buf[MSG_SIZ];
13396     char *p;
13397
13398     p = strchr(str, ' ');
13399     if (p == NULL) return str;
13400     strncpy(buf, str, p - str);
13401     buf[p - str] = NULLCHAR;
13402     return buf;
13403 }
13404
13405 #define PGN_MAX_LINE 75
13406
13407 #define PGN_SIDE_WHITE  0
13408 #define PGN_SIDE_BLACK  1
13409
13410 static int
13411 FindFirstMoveOutOfBook (int side)
13412 {
13413     int result = -1;
13414
13415     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13416         int index = backwardMostMove;
13417         int has_book_hit = 0;
13418
13419         if( (index % 2) != side ) {
13420             index++;
13421         }
13422
13423         while( index < forwardMostMove ) {
13424             /* Check to see if engine is in book */
13425             int depth = pvInfoList[index].depth;
13426             int score = pvInfoList[index].score;
13427             int in_book = 0;
13428
13429             if( depth <= 2 ) {
13430                 in_book = 1;
13431             }
13432             else if( score == 0 && depth == 63 ) {
13433                 in_book = 1; /* Zappa */
13434             }
13435             else if( score == 2 && depth == 99 ) {
13436                 in_book = 1; /* Abrok */
13437             }
13438
13439             has_book_hit += in_book;
13440
13441             if( ! in_book ) {
13442                 result = index;
13443
13444                 break;
13445             }
13446
13447             index += 2;
13448         }
13449     }
13450
13451     return result;
13452 }
13453
13454 void
13455 GetOutOfBookInfo (char * buf)
13456 {
13457     int oob[2];
13458     int i;
13459     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13460
13461     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13462     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13463
13464     *buf = '\0';
13465
13466     if( oob[0] >= 0 || oob[1] >= 0 ) {
13467         for( i=0; i<2; i++ ) {
13468             int idx = oob[i];
13469
13470             if( idx >= 0 ) {
13471                 if( i > 0 && oob[0] >= 0 ) {
13472                     strcat( buf, "   " );
13473                 }
13474
13475                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13476                 sprintf( buf+strlen(buf), "%s%.2f",
13477                     pvInfoList[idx].score >= 0 ? "+" : "",
13478                     pvInfoList[idx].score / 100.0 );
13479             }
13480         }
13481     }
13482 }
13483
13484 /* Save game in PGN style */
13485 static void
13486 SaveGamePGN2 (FILE *f)
13487 {
13488     int i, offset, linelen, newblock;
13489 //    char *movetext;
13490     char numtext[32];
13491     int movelen, numlen, blank;
13492     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13493
13494     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13495
13496     PrintPGNTags(f, &gameInfo);
13497
13498     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13499
13500     if (backwardMostMove > 0 || startedFromSetupPosition) {
13501         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13502         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13503         fprintf(f, "\n{--------------\n");
13504         PrintPosition(f, backwardMostMove);
13505         fprintf(f, "--------------}\n");
13506         free(fen);
13507     }
13508     else {
13509         /* [AS] Out of book annotation */
13510         if( appData.saveOutOfBookInfo ) {
13511             char buf[64];
13512
13513             GetOutOfBookInfo( buf );
13514
13515             if( buf[0] != '\0' ) {
13516                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13517             }
13518         }
13519
13520         fprintf(f, "\n");
13521     }
13522
13523     i = backwardMostMove;
13524     linelen = 0;
13525     newblock = TRUE;
13526
13527     while (i < forwardMostMove) {
13528         /* Print comments preceding this move */
13529         if (commentList[i] != NULL) {
13530             if (linelen > 0) fprintf(f, "\n");
13531             fprintf(f, "%s", commentList[i]);
13532             linelen = 0;
13533             newblock = TRUE;
13534         }
13535
13536         /* Format move number */
13537         if ((i % 2) == 0)
13538           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13539         else
13540           if (newblock)
13541             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13542           else
13543             numtext[0] = NULLCHAR;
13544
13545         numlen = strlen(numtext);
13546         newblock = FALSE;
13547
13548         /* Print move number */
13549         blank = linelen > 0 && numlen > 0;
13550         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13551             fprintf(f, "\n");
13552             linelen = 0;
13553             blank = 0;
13554         }
13555         if (blank) {
13556             fprintf(f, " ");
13557             linelen++;
13558         }
13559         fprintf(f, "%s", numtext);
13560         linelen += numlen;
13561
13562         /* Get move */
13563         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13564         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13565
13566         /* Print move */
13567         blank = linelen > 0 && movelen > 0;
13568         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13569             fprintf(f, "\n");
13570             linelen = 0;
13571             blank = 0;
13572         }
13573         if (blank) {
13574             fprintf(f, " ");
13575             linelen++;
13576         }
13577         fprintf(f, "%s", move_buffer);
13578         linelen += movelen;
13579
13580         /* [AS] Add PV info if present */
13581         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13582             /* [HGM] add time */
13583             char buf[MSG_SIZ]; int seconds;
13584
13585             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13586
13587             if( seconds <= 0)
13588               buf[0] = 0;
13589             else
13590               if( seconds < 30 )
13591                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13592               else
13593                 {
13594                   seconds = (seconds + 4)/10; // round to full seconds
13595                   if( seconds < 60 )
13596                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13597                   else
13598                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13599                 }
13600
13601             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13602                       pvInfoList[i].score >= 0 ? "+" : "",
13603                       pvInfoList[i].score / 100.0,
13604                       pvInfoList[i].depth,
13605                       buf );
13606
13607             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13608
13609             /* Print score/depth */
13610             blank = linelen > 0 && movelen > 0;
13611             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13612                 fprintf(f, "\n");
13613                 linelen = 0;
13614                 blank = 0;
13615             }
13616             if (blank) {
13617                 fprintf(f, " ");
13618                 linelen++;
13619             }
13620             fprintf(f, "%s", move_buffer);
13621             linelen += movelen;
13622         }
13623
13624         i++;
13625     }
13626
13627     /* Start a new line */
13628     if (linelen > 0) fprintf(f, "\n");
13629
13630     /* Print comments after last move */
13631     if (commentList[i] != NULL) {
13632         fprintf(f, "%s\n", commentList[i]);
13633     }
13634
13635     /* Print result */
13636     if (gameInfo.resultDetails != NULL &&
13637         gameInfo.resultDetails[0] != NULLCHAR) {
13638         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13639         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13640            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13641             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13642         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13643     } else {
13644         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13645     }
13646 }
13647
13648 /* Save game in PGN style and close the file */
13649 int
13650 SaveGamePGN (FILE *f)
13651 {
13652     SaveGamePGN2(f);
13653     fclose(f);
13654     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13655     return TRUE;
13656 }
13657
13658 /* Save game in old style and close the file */
13659 int
13660 SaveGameOldStyle (FILE *f)
13661 {
13662     int i, offset;
13663     time_t tm;
13664
13665     tm = time((time_t *) NULL);
13666
13667     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13668     PrintOpponents(f);
13669
13670     if (backwardMostMove > 0 || startedFromSetupPosition) {
13671         fprintf(f, "\n[--------------\n");
13672         PrintPosition(f, backwardMostMove);
13673         fprintf(f, "--------------]\n");
13674     } else {
13675         fprintf(f, "\n");
13676     }
13677
13678     i = backwardMostMove;
13679     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13680
13681     while (i < forwardMostMove) {
13682         if (commentList[i] != NULL) {
13683             fprintf(f, "[%s]\n", commentList[i]);
13684         }
13685
13686         if ((i % 2) == 1) {
13687             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13688             i++;
13689         } else {
13690             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13691             i++;
13692             if (commentList[i] != NULL) {
13693                 fprintf(f, "\n");
13694                 continue;
13695             }
13696             if (i >= forwardMostMove) {
13697                 fprintf(f, "\n");
13698                 break;
13699             }
13700             fprintf(f, "%s\n", parseList[i]);
13701             i++;
13702         }
13703     }
13704
13705     if (commentList[i] != NULL) {
13706         fprintf(f, "[%s]\n", commentList[i]);
13707     }
13708
13709     /* This isn't really the old style, but it's close enough */
13710     if (gameInfo.resultDetails != NULL &&
13711         gameInfo.resultDetails[0] != NULLCHAR) {
13712         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13713                 gameInfo.resultDetails);
13714     } else {
13715         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13716     }
13717
13718     fclose(f);
13719     return TRUE;
13720 }
13721
13722 /* Save the current game to open file f and close the file */
13723 int
13724 SaveGame (FILE *f, int dummy, char *dummy2)
13725 {
13726     if (gameMode == EditPosition) EditPositionDone(TRUE);
13727     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13728     if (appData.oldSaveStyle)
13729       return SaveGameOldStyle(f);
13730     else
13731       return SaveGamePGN(f);
13732 }
13733
13734 /* Save the current position to the given file */
13735 int
13736 SavePositionToFile (char *filename)
13737 {
13738     FILE *f;
13739     char buf[MSG_SIZ];
13740
13741     if (strcmp(filename, "-") == 0) {
13742         return SavePosition(stdout, 0, NULL);
13743     } else {
13744         f = fopen(filename, "a");
13745         if (f == NULL) {
13746             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13747             DisplayError(buf, errno);
13748             return FALSE;
13749         } else {
13750             safeStrCpy(buf, lastMsg, MSG_SIZ);
13751             DisplayMessage(_("Waiting for access to save file"), "");
13752             flock(fileno(f), LOCK_EX); // [HGM] lock
13753             DisplayMessage(_("Saving position"), "");
13754             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13755             SavePosition(f, 0, NULL);
13756             DisplayMessage(buf, "");
13757             return TRUE;
13758         }
13759     }
13760 }
13761
13762 /* Save the current position to the given open file and close the file */
13763 int
13764 SavePosition (FILE *f, int dummy, char *dummy2)
13765 {
13766     time_t tm;
13767     char *fen;
13768
13769     if (gameMode == EditPosition) EditPositionDone(TRUE);
13770     if (appData.oldSaveStyle) {
13771         tm = time((time_t *) NULL);
13772
13773         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13774         PrintOpponents(f);
13775         fprintf(f, "[--------------\n");
13776         PrintPosition(f, currentMove);
13777         fprintf(f, "--------------]\n");
13778     } else {
13779         fen = PositionToFEN(currentMove, NULL, 1);
13780         fprintf(f, "%s\n", fen);
13781         free(fen);
13782     }
13783     fclose(f);
13784     return TRUE;
13785 }
13786
13787 void
13788 ReloadCmailMsgEvent (int unregister)
13789 {
13790 #if !WIN32
13791     static char *inFilename = NULL;
13792     static char *outFilename;
13793     int i;
13794     struct stat inbuf, outbuf;
13795     int status;
13796
13797     /* Any registered moves are unregistered if unregister is set, */
13798     /* i.e. invoked by the signal handler */
13799     if (unregister) {
13800         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13801             cmailMoveRegistered[i] = FALSE;
13802             if (cmailCommentList[i] != NULL) {
13803                 free(cmailCommentList[i]);
13804                 cmailCommentList[i] = NULL;
13805             }
13806         }
13807         nCmailMovesRegistered = 0;
13808     }
13809
13810     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13811         cmailResult[i] = CMAIL_NOT_RESULT;
13812     }
13813     nCmailResults = 0;
13814
13815     if (inFilename == NULL) {
13816         /* Because the filenames are static they only get malloced once  */
13817         /* and they never get freed                                      */
13818         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13819         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13820
13821         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13822         sprintf(outFilename, "%s.out", appData.cmailGameName);
13823     }
13824
13825     status = stat(outFilename, &outbuf);
13826     if (status < 0) {
13827         cmailMailedMove = FALSE;
13828     } else {
13829         status = stat(inFilename, &inbuf);
13830         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13831     }
13832
13833     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13834        counts the games, notes how each one terminated, etc.
13835
13836        It would be nice to remove this kludge and instead gather all
13837        the information while building the game list.  (And to keep it
13838        in the game list nodes instead of having a bunch of fixed-size
13839        parallel arrays.)  Note this will require getting each game's
13840        termination from the PGN tags, as the game list builder does
13841        not process the game moves.  --mann
13842        */
13843     cmailMsgLoaded = TRUE;
13844     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13845
13846     /* Load first game in the file or popup game menu */
13847     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13848
13849 #endif /* !WIN32 */
13850     return;
13851 }
13852
13853 int
13854 RegisterMove ()
13855 {
13856     FILE *f;
13857     char string[MSG_SIZ];
13858
13859     if (   cmailMailedMove
13860         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13861         return TRUE;            /* Allow free viewing  */
13862     }
13863
13864     /* Unregister move to ensure that we don't leave RegisterMove        */
13865     /* with the move registered when the conditions for registering no   */
13866     /* longer hold                                                       */
13867     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13868         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13869         nCmailMovesRegistered --;
13870
13871         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13872           {
13873               free(cmailCommentList[lastLoadGameNumber - 1]);
13874               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13875           }
13876     }
13877
13878     if (cmailOldMove == -1) {
13879         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13880         return FALSE;
13881     }
13882
13883     if (currentMove > cmailOldMove + 1) {
13884         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13885         return FALSE;
13886     }
13887
13888     if (currentMove < cmailOldMove) {
13889         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13890         return FALSE;
13891     }
13892
13893     if (forwardMostMove > currentMove) {
13894         /* Silently truncate extra moves */
13895         TruncateGame();
13896     }
13897
13898     if (   (currentMove == cmailOldMove + 1)
13899         || (   (currentMove == cmailOldMove)
13900             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13901                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13902         if (gameInfo.result != GameUnfinished) {
13903             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13904         }
13905
13906         if (commentList[currentMove] != NULL) {
13907             cmailCommentList[lastLoadGameNumber - 1]
13908               = StrSave(commentList[currentMove]);
13909         }
13910         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13911
13912         if (appData.debugMode)
13913           fprintf(debugFP, "Saving %s for game %d\n",
13914                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13915
13916         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13917
13918         f = fopen(string, "w");
13919         if (appData.oldSaveStyle) {
13920             SaveGameOldStyle(f); /* also closes the file */
13921
13922             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13923             f = fopen(string, "w");
13924             SavePosition(f, 0, NULL); /* also closes the file */
13925         } else {
13926             fprintf(f, "{--------------\n");
13927             PrintPosition(f, currentMove);
13928             fprintf(f, "--------------}\n\n");
13929
13930             SaveGame(f, 0, NULL); /* also closes the file*/
13931         }
13932
13933         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13934         nCmailMovesRegistered ++;
13935     } else if (nCmailGames == 1) {
13936         DisplayError(_("You have not made a move yet"), 0);
13937         return FALSE;
13938     }
13939
13940     return TRUE;
13941 }
13942
13943 void
13944 MailMoveEvent ()
13945 {
13946 #if !WIN32
13947     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13948     FILE *commandOutput;
13949     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13950     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13951     int nBuffers;
13952     int i;
13953     int archived;
13954     char *arcDir;
13955
13956     if (! cmailMsgLoaded) {
13957         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13958         return;
13959     }
13960
13961     if (nCmailGames == nCmailResults) {
13962         DisplayError(_("No unfinished games"), 0);
13963         return;
13964     }
13965
13966 #if CMAIL_PROHIBIT_REMAIL
13967     if (cmailMailedMove) {
13968       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);
13969         DisplayError(msg, 0);
13970         return;
13971     }
13972 #endif
13973
13974     if (! (cmailMailedMove || RegisterMove())) return;
13975
13976     if (   cmailMailedMove
13977         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13978       snprintf(string, MSG_SIZ, partCommandString,
13979                appData.debugMode ? " -v" : "", appData.cmailGameName);
13980         commandOutput = popen(string, "r");
13981
13982         if (commandOutput == NULL) {
13983             DisplayError(_("Failed to invoke cmail"), 0);
13984         } else {
13985             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13986                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13987             }
13988             if (nBuffers > 1) {
13989                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13990                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13991                 nBytes = MSG_SIZ - 1;
13992             } else {
13993                 (void) memcpy(msg, buffer, nBytes);
13994             }
13995             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13996
13997             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13998                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13999
14000                 archived = TRUE;
14001                 for (i = 0; i < nCmailGames; i ++) {
14002                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14003                         archived = FALSE;
14004                     }
14005                 }
14006                 if (   archived
14007                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14008                         != NULL)) {
14009                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14010                            arcDir,
14011                            appData.cmailGameName,
14012                            gameInfo.date);
14013                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14014                     cmailMsgLoaded = FALSE;
14015                 }
14016             }
14017
14018             DisplayInformation(msg);
14019             pclose(commandOutput);
14020         }
14021     } else {
14022         if ((*cmailMsg) != '\0') {
14023             DisplayInformation(cmailMsg);
14024         }
14025     }
14026
14027     return;
14028 #endif /* !WIN32 */
14029 }
14030
14031 char *
14032 CmailMsg ()
14033 {
14034 #if WIN32
14035     return NULL;
14036 #else
14037     int  prependComma = 0;
14038     char number[5];
14039     char string[MSG_SIZ];       /* Space for game-list */
14040     int  i;
14041
14042     if (!cmailMsgLoaded) return "";
14043
14044     if (cmailMailedMove) {
14045       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14046     } else {
14047         /* Create a list of games left */
14048       snprintf(string, MSG_SIZ, "[");
14049         for (i = 0; i < nCmailGames; i ++) {
14050             if (! (   cmailMoveRegistered[i]
14051                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14052                 if (prependComma) {
14053                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14054                 } else {
14055                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14056                     prependComma = 1;
14057                 }
14058
14059                 strcat(string, number);
14060             }
14061         }
14062         strcat(string, "]");
14063
14064         if (nCmailMovesRegistered + nCmailResults == 0) {
14065             switch (nCmailGames) {
14066               case 1:
14067                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14068                 break;
14069
14070               case 2:
14071                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14072                 break;
14073
14074               default:
14075                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14076                          nCmailGames);
14077                 break;
14078             }
14079         } else {
14080             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14081               case 1:
14082                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14083                          string);
14084                 break;
14085
14086               case 0:
14087                 if (nCmailResults == nCmailGames) {
14088                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14089                 } else {
14090                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14091                 }
14092                 break;
14093
14094               default:
14095                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14096                          string);
14097             }
14098         }
14099     }
14100     return cmailMsg;
14101 #endif /* WIN32 */
14102 }
14103
14104 void
14105 ResetGameEvent ()
14106 {
14107     if (gameMode == Training)
14108       SetTrainingModeOff();
14109
14110     Reset(TRUE, TRUE);
14111     cmailMsgLoaded = FALSE;
14112     if (appData.icsActive) {
14113       SendToICS(ics_prefix);
14114       SendToICS("refresh\n");
14115     }
14116 }
14117
14118 void
14119 ExitEvent (int status)
14120 {
14121     exiting++;
14122     if (exiting > 2) {
14123       /* Give up on clean exit */
14124       exit(status);
14125     }
14126     if (exiting > 1) {
14127       /* Keep trying for clean exit */
14128       return;
14129     }
14130
14131     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14132     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14133
14134     if (telnetISR != NULL) {
14135       RemoveInputSource(telnetISR);
14136     }
14137     if (icsPR != NoProc) {
14138       DestroyChildProcess(icsPR, TRUE);
14139     }
14140
14141     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14142     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14143
14144     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14145     /* make sure this other one finishes before killing it!                  */
14146     if(endingGame) { int count = 0;
14147         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14148         while(endingGame && count++ < 10) DoSleep(1);
14149         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14150     }
14151
14152     /* Kill off chess programs */
14153     if (first.pr != NoProc) {
14154         ExitAnalyzeMode();
14155
14156         DoSleep( appData.delayBeforeQuit );
14157         SendToProgram("quit\n", &first);
14158         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14159     }
14160     if (second.pr != NoProc) {
14161         DoSleep( appData.delayBeforeQuit );
14162         SendToProgram("quit\n", &second);
14163         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14164     }
14165     if (first.isr != NULL) {
14166         RemoveInputSource(first.isr);
14167     }
14168     if (second.isr != NULL) {
14169         RemoveInputSource(second.isr);
14170     }
14171
14172     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14173     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14174
14175     ShutDownFrontEnd();
14176     exit(status);
14177 }
14178
14179 void
14180 PauseEngine (ChessProgramState *cps)
14181 {
14182     SendToProgram("pause\n", cps);
14183     cps->pause = 2;
14184 }
14185
14186 void
14187 UnPauseEngine (ChessProgramState *cps)
14188 {
14189     SendToProgram("resume\n", cps);
14190     cps->pause = 1;
14191 }
14192
14193 void
14194 PauseEvent ()
14195 {
14196     if (appData.debugMode)
14197         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14198     if (pausing) {
14199         pausing = FALSE;
14200         ModeHighlight();
14201         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14202             StartClocks();
14203             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14204                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14205                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14206             }
14207             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14208             HandleMachineMove(stashedInputMove, stalledEngine);
14209             stalledEngine = NULL;
14210             return;
14211         }
14212         if (gameMode == MachinePlaysWhite ||
14213             gameMode == TwoMachinesPlay   ||
14214             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14215             if(first.pause)  UnPauseEngine(&first);
14216             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14217             if(second.pause) UnPauseEngine(&second);
14218             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14219             StartClocks();
14220         } else {
14221             DisplayBothClocks();
14222         }
14223         if (gameMode == PlayFromGameFile) {
14224             if (appData.timeDelay >= 0)
14225                 AutoPlayGameLoop();
14226         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14227             Reset(FALSE, TRUE);
14228             SendToICS(ics_prefix);
14229             SendToICS("refresh\n");
14230         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14231             ForwardInner(forwardMostMove);
14232         }
14233         pauseExamInvalid = FALSE;
14234     } else {
14235         switch (gameMode) {
14236           default:
14237             return;
14238           case IcsExamining:
14239             pauseExamForwardMostMove = forwardMostMove;
14240             pauseExamInvalid = FALSE;
14241             /* fall through */
14242           case IcsObserving:
14243           case IcsPlayingWhite:
14244           case IcsPlayingBlack:
14245             pausing = TRUE;
14246             ModeHighlight();
14247             return;
14248           case PlayFromGameFile:
14249             (void) StopLoadGameTimer();
14250             pausing = TRUE;
14251             ModeHighlight();
14252             break;
14253           case BeginningOfGame:
14254             if (appData.icsActive) return;
14255             /* else fall through */
14256           case MachinePlaysWhite:
14257           case MachinePlaysBlack:
14258           case TwoMachinesPlay:
14259             if (forwardMostMove == 0)
14260               return;           /* don't pause if no one has moved */
14261             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14262                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14263                 if(onMove->pause) {           // thinking engine can be paused
14264                     PauseEngine(onMove);      // do it
14265                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14266                         PauseEngine(onMove->other);
14267                     else
14268                         SendToProgram("easy\n", onMove->other);
14269                     StopClocks();
14270                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14271             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14272                 if(first.pause) {
14273                     PauseEngine(&first);
14274                     StopClocks();
14275                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14276             } else { // human on move, pause pondering by either method
14277                 if(first.pause)
14278                     PauseEngine(&first);
14279                 else if(appData.ponderNextMove)
14280                     SendToProgram("easy\n", &first);
14281                 StopClocks();
14282             }
14283             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14284           case AnalyzeMode:
14285             pausing = TRUE;
14286             ModeHighlight();
14287             break;
14288         }
14289     }
14290 }
14291
14292 void
14293 EditCommentEvent ()
14294 {
14295     char title[MSG_SIZ];
14296
14297     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14298       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14299     } else {
14300       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14301                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14302                parseList[currentMove - 1]);
14303     }
14304
14305     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14306 }
14307
14308
14309 void
14310 EditTagsEvent ()
14311 {
14312     char *tags = PGNTags(&gameInfo);
14313     bookUp = FALSE;
14314     EditTagsPopUp(tags, NULL);
14315     free(tags);
14316 }
14317
14318 void
14319 ToggleSecond ()
14320 {
14321   if(second.analyzing) {
14322     SendToProgram("exit\n", &second);
14323     second.analyzing = FALSE;
14324   } else {
14325     if (second.pr == NoProc) StartChessProgram(&second);
14326     InitChessProgram(&second, FALSE);
14327     FeedMovesToProgram(&second, currentMove);
14328
14329     SendToProgram("analyze\n", &second);
14330     second.analyzing = TRUE;
14331   }
14332 }
14333
14334 /* Toggle ShowThinking */
14335 void
14336 ToggleShowThinking()
14337 {
14338   appData.showThinking = !appData.showThinking;
14339   ShowThinkingEvent();
14340 }
14341
14342 int
14343 AnalyzeModeEvent ()
14344 {
14345     char buf[MSG_SIZ];
14346
14347     if (!first.analysisSupport) {
14348       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14349       DisplayError(buf, 0);
14350       return 0;
14351     }
14352     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14353     if (appData.icsActive) {
14354         if (gameMode != IcsObserving) {
14355           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14356             DisplayError(buf, 0);
14357             /* secure check */
14358             if (appData.icsEngineAnalyze) {
14359                 if (appData.debugMode)
14360                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14361                 ExitAnalyzeMode();
14362                 ModeHighlight();
14363             }
14364             return 0;
14365         }
14366         /* if enable, user wants to disable icsEngineAnalyze */
14367         if (appData.icsEngineAnalyze) {
14368                 ExitAnalyzeMode();
14369                 ModeHighlight();
14370                 return 0;
14371         }
14372         appData.icsEngineAnalyze = TRUE;
14373         if (appData.debugMode)
14374             fprintf(debugFP, "ICS engine analyze starting... \n");
14375     }
14376
14377     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14378     if (appData.noChessProgram || gameMode == AnalyzeMode)
14379       return 0;
14380
14381     if (gameMode != AnalyzeFile) {
14382         if (!appData.icsEngineAnalyze) {
14383                EditGameEvent();
14384                if (gameMode != EditGame) return 0;
14385         }
14386         if (!appData.showThinking) ToggleShowThinking();
14387         ResurrectChessProgram();
14388         SendToProgram("analyze\n", &first);
14389         first.analyzing = TRUE;
14390         /*first.maybeThinking = TRUE;*/
14391         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14392         EngineOutputPopUp();
14393     }
14394     if (!appData.icsEngineAnalyze) {
14395         gameMode = AnalyzeMode;
14396         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14397     }
14398     pausing = FALSE;
14399     ModeHighlight();
14400     SetGameInfo();
14401
14402     StartAnalysisClock();
14403     GetTimeMark(&lastNodeCountTime);
14404     lastNodeCount = 0;
14405     return 1;
14406 }
14407
14408 void
14409 AnalyzeFileEvent ()
14410 {
14411     if (appData.noChessProgram || gameMode == AnalyzeFile)
14412       return;
14413
14414     if (!first.analysisSupport) {
14415       char buf[MSG_SIZ];
14416       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14417       DisplayError(buf, 0);
14418       return;
14419     }
14420
14421     if (gameMode != AnalyzeMode) {
14422         keepInfo = 1; // mere annotating should not alter PGN tags
14423         EditGameEvent();
14424         keepInfo = 0;
14425         if (gameMode != EditGame) return;
14426         if (!appData.showThinking) ToggleShowThinking();
14427         ResurrectChessProgram();
14428         SendToProgram("analyze\n", &first);
14429         first.analyzing = TRUE;
14430         /*first.maybeThinking = TRUE;*/
14431         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14432         EngineOutputPopUp();
14433     }
14434     gameMode = AnalyzeFile;
14435     pausing = FALSE;
14436     ModeHighlight();
14437
14438     StartAnalysisClock();
14439     GetTimeMark(&lastNodeCountTime);
14440     lastNodeCount = 0;
14441     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14442     AnalysisPeriodicEvent(1);
14443 }
14444
14445 void
14446 MachineWhiteEvent ()
14447 {
14448     char buf[MSG_SIZ];
14449     char *bookHit = NULL;
14450
14451     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14452       return;
14453
14454
14455     if (gameMode == PlayFromGameFile ||
14456         gameMode == TwoMachinesPlay  ||
14457         gameMode == Training         ||
14458         gameMode == AnalyzeMode      ||
14459         gameMode == EndOfGame)
14460         EditGameEvent();
14461
14462     if (gameMode == EditPosition)
14463         EditPositionDone(TRUE);
14464
14465     if (!WhiteOnMove(currentMove)) {
14466         DisplayError(_("It is not White's turn"), 0);
14467         return;
14468     }
14469
14470     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14471       ExitAnalyzeMode();
14472
14473     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14474         gameMode == AnalyzeFile)
14475         TruncateGame();
14476
14477     ResurrectChessProgram();    /* in case it isn't running */
14478     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14479         gameMode = MachinePlaysWhite;
14480         ResetClocks();
14481     } else
14482     gameMode = MachinePlaysWhite;
14483     pausing = FALSE;
14484     ModeHighlight();
14485     SetGameInfo();
14486     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14487     DisplayTitle(buf);
14488     if (first.sendName) {
14489       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14490       SendToProgram(buf, &first);
14491     }
14492     if (first.sendTime) {
14493       if (first.useColors) {
14494         SendToProgram("black\n", &first); /*gnu kludge*/
14495       }
14496       SendTimeRemaining(&first, TRUE);
14497     }
14498     if (first.useColors) {
14499       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14500     }
14501     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14502     SetMachineThinkingEnables();
14503     first.maybeThinking = TRUE;
14504     StartClocks();
14505     firstMove = FALSE;
14506
14507     if (appData.autoFlipView && !flipView) {
14508       flipView = !flipView;
14509       DrawPosition(FALSE, NULL);
14510       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14511     }
14512
14513     if(bookHit) { // [HGM] book: simulate book reply
14514         static char bookMove[MSG_SIZ]; // a bit generous?
14515
14516         programStats.nodes = programStats.depth = programStats.time =
14517         programStats.score = programStats.got_only_move = 0;
14518         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14519
14520         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14521         strcat(bookMove, bookHit);
14522         HandleMachineMove(bookMove, &first);
14523     }
14524 }
14525
14526 void
14527 MachineBlackEvent ()
14528 {
14529   char buf[MSG_SIZ];
14530   char *bookHit = NULL;
14531
14532     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14533         return;
14534
14535
14536     if (gameMode == PlayFromGameFile ||
14537         gameMode == TwoMachinesPlay  ||
14538         gameMode == Training         ||
14539         gameMode == AnalyzeMode      ||
14540         gameMode == EndOfGame)
14541         EditGameEvent();
14542
14543     if (gameMode == EditPosition)
14544         EditPositionDone(TRUE);
14545
14546     if (WhiteOnMove(currentMove)) {
14547         DisplayError(_("It is not Black's turn"), 0);
14548         return;
14549     }
14550
14551     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14552       ExitAnalyzeMode();
14553
14554     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14555         gameMode == AnalyzeFile)
14556         TruncateGame();
14557
14558     ResurrectChessProgram();    /* in case it isn't running */
14559     gameMode = MachinePlaysBlack;
14560     pausing = FALSE;
14561     ModeHighlight();
14562     SetGameInfo();
14563     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14564     DisplayTitle(buf);
14565     if (first.sendName) {
14566       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14567       SendToProgram(buf, &first);
14568     }
14569     if (first.sendTime) {
14570       if (first.useColors) {
14571         SendToProgram("white\n", &first); /*gnu kludge*/
14572       }
14573       SendTimeRemaining(&first, FALSE);
14574     }
14575     if (first.useColors) {
14576       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14577     }
14578     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14579     SetMachineThinkingEnables();
14580     first.maybeThinking = TRUE;
14581     StartClocks();
14582
14583     if (appData.autoFlipView && flipView) {
14584       flipView = !flipView;
14585       DrawPosition(FALSE, NULL);
14586       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14587     }
14588     if(bookHit) { // [HGM] book: simulate book reply
14589         static char bookMove[MSG_SIZ]; // a bit generous?
14590
14591         programStats.nodes = programStats.depth = programStats.time =
14592         programStats.score = programStats.got_only_move = 0;
14593         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14594
14595         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14596         strcat(bookMove, bookHit);
14597         HandleMachineMove(bookMove, &first);
14598     }
14599 }
14600
14601
14602 void
14603 DisplayTwoMachinesTitle ()
14604 {
14605     char buf[MSG_SIZ];
14606     if (appData.matchGames > 0) {
14607         if(appData.tourneyFile[0]) {
14608           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14609                    gameInfo.white, _("vs."), gameInfo.black,
14610                    nextGame+1, appData.matchGames+1,
14611                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14612         } else
14613         if (first.twoMachinesColor[0] == 'w') {
14614           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14615                    gameInfo.white, _("vs."),  gameInfo.black,
14616                    first.matchWins, second.matchWins,
14617                    matchGame - 1 - (first.matchWins + second.matchWins));
14618         } else {
14619           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14620                    gameInfo.white, _("vs."), gameInfo.black,
14621                    second.matchWins, first.matchWins,
14622                    matchGame - 1 - (first.matchWins + second.matchWins));
14623         }
14624     } else {
14625       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14626     }
14627     DisplayTitle(buf);
14628 }
14629
14630 void
14631 SettingsMenuIfReady ()
14632 {
14633   if (second.lastPing != second.lastPong) {
14634     DisplayMessage("", _("Waiting for second chess program"));
14635     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14636     return;
14637   }
14638   ThawUI();
14639   DisplayMessage("", "");
14640   SettingsPopUp(&second);
14641 }
14642
14643 int
14644 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14645 {
14646     char buf[MSG_SIZ];
14647     if (cps->pr == NoProc) {
14648         StartChessProgram(cps);
14649         if (cps->protocolVersion == 1) {
14650           retry();
14651           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14652         } else {
14653           /* kludge: allow timeout for initial "feature" command */
14654           if(retry != TwoMachinesEventIfReady) FreezeUI();
14655           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14656           DisplayMessage("", buf);
14657           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14658         }
14659         return 1;
14660     }
14661     return 0;
14662 }
14663
14664 void
14665 TwoMachinesEvent P((void))
14666 {
14667     int i;
14668     char buf[MSG_SIZ];
14669     ChessProgramState *onmove;
14670     char *bookHit = NULL;
14671     static int stalling = 0;
14672     TimeMark now;
14673     long wait;
14674
14675     if (appData.noChessProgram) return;
14676
14677     switch (gameMode) {
14678       case TwoMachinesPlay:
14679         return;
14680       case MachinePlaysWhite:
14681       case MachinePlaysBlack:
14682         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14683             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14684             return;
14685         }
14686         /* fall through */
14687       case BeginningOfGame:
14688       case PlayFromGameFile:
14689       case EndOfGame:
14690         EditGameEvent();
14691         if (gameMode != EditGame) return;
14692         break;
14693       case EditPosition:
14694         EditPositionDone(TRUE);
14695         break;
14696       case AnalyzeMode:
14697       case AnalyzeFile:
14698         ExitAnalyzeMode();
14699         break;
14700       case EditGame:
14701       default:
14702         break;
14703     }
14704
14705 //    forwardMostMove = currentMove;
14706     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14707     startingEngine = TRUE;
14708
14709     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14710
14711     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14712     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14713       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14714       return;
14715     }
14716     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14717
14718     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14719                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14720         startingEngine = matchMode = FALSE;
14721         DisplayError("second engine does not play this", 0);
14722         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14723         EditGameEvent(); // switch back to EditGame mode
14724         return;
14725     }
14726
14727     if(!stalling) {
14728       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14729       SendToProgram("force\n", &second);
14730       stalling = 1;
14731       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14732       return;
14733     }
14734     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14735     if(appData.matchPause>10000 || appData.matchPause<10)
14736                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14737     wait = SubtractTimeMarks(&now, &pauseStart);
14738     if(wait < appData.matchPause) {
14739         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14740         return;
14741     }
14742     // we are now committed to starting the game
14743     stalling = 0;
14744     DisplayMessage("", "");
14745     if (startedFromSetupPosition) {
14746         SendBoard(&second, backwardMostMove);
14747     if (appData.debugMode) {
14748         fprintf(debugFP, "Two Machines\n");
14749     }
14750     }
14751     for (i = backwardMostMove; i < forwardMostMove; i++) {
14752         SendMoveToProgram(i, &second);
14753     }
14754
14755     gameMode = TwoMachinesPlay;
14756     pausing = startingEngine = FALSE;
14757     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14758     SetGameInfo();
14759     DisplayTwoMachinesTitle();
14760     firstMove = TRUE;
14761     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14762         onmove = &first;
14763     } else {
14764         onmove = &second;
14765     }
14766     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14767     SendToProgram(first.computerString, &first);
14768     if (first.sendName) {
14769       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14770       SendToProgram(buf, &first);
14771     }
14772     SendToProgram(second.computerString, &second);
14773     if (second.sendName) {
14774       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14775       SendToProgram(buf, &second);
14776     }
14777
14778     ResetClocks();
14779     if (!first.sendTime || !second.sendTime) {
14780         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14781         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14782     }
14783     if (onmove->sendTime) {
14784       if (onmove->useColors) {
14785         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14786       }
14787       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14788     }
14789     if (onmove->useColors) {
14790       SendToProgram(onmove->twoMachinesColor, onmove);
14791     }
14792     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14793 //    SendToProgram("go\n", onmove);
14794     onmove->maybeThinking = TRUE;
14795     SetMachineThinkingEnables();
14796
14797     StartClocks();
14798
14799     if(bookHit) { // [HGM] book: simulate book reply
14800         static char bookMove[MSG_SIZ]; // a bit generous?
14801
14802         programStats.nodes = programStats.depth = programStats.time =
14803         programStats.score = programStats.got_only_move = 0;
14804         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14805
14806         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14807         strcat(bookMove, bookHit);
14808         savedMessage = bookMove; // args for deferred call
14809         savedState = onmove;
14810         ScheduleDelayedEvent(DeferredBookMove, 1);
14811     }
14812 }
14813
14814 void
14815 TrainingEvent ()
14816 {
14817     if (gameMode == Training) {
14818       SetTrainingModeOff();
14819       gameMode = PlayFromGameFile;
14820       DisplayMessage("", _("Training mode off"));
14821     } else {
14822       gameMode = Training;
14823       animateTraining = appData.animate;
14824
14825       /* make sure we are not already at the end of the game */
14826       if (currentMove < forwardMostMove) {
14827         SetTrainingModeOn();
14828         DisplayMessage("", _("Training mode on"));
14829       } else {
14830         gameMode = PlayFromGameFile;
14831         DisplayError(_("Already at end of game"), 0);
14832       }
14833     }
14834     ModeHighlight();
14835 }
14836
14837 void
14838 IcsClientEvent ()
14839 {
14840     if (!appData.icsActive) return;
14841     switch (gameMode) {
14842       case IcsPlayingWhite:
14843       case IcsPlayingBlack:
14844       case IcsObserving:
14845       case IcsIdle:
14846       case BeginningOfGame:
14847       case IcsExamining:
14848         return;
14849
14850       case EditGame:
14851         break;
14852
14853       case EditPosition:
14854         EditPositionDone(TRUE);
14855         break;
14856
14857       case AnalyzeMode:
14858       case AnalyzeFile:
14859         ExitAnalyzeMode();
14860         break;
14861
14862       default:
14863         EditGameEvent();
14864         break;
14865     }
14866
14867     gameMode = IcsIdle;
14868     ModeHighlight();
14869     return;
14870 }
14871
14872 void
14873 EditGameEvent ()
14874 {
14875     int i;
14876
14877     switch (gameMode) {
14878       case Training:
14879         SetTrainingModeOff();
14880         break;
14881       case MachinePlaysWhite:
14882       case MachinePlaysBlack:
14883       case BeginningOfGame:
14884         SendToProgram("force\n", &first);
14885         SetUserThinkingEnables();
14886         break;
14887       case PlayFromGameFile:
14888         (void) StopLoadGameTimer();
14889         if (gameFileFP != NULL) {
14890             gameFileFP = NULL;
14891         }
14892         break;
14893       case EditPosition:
14894         EditPositionDone(TRUE);
14895         break;
14896       case AnalyzeMode:
14897       case AnalyzeFile:
14898         ExitAnalyzeMode();
14899         SendToProgram("force\n", &first);
14900         break;
14901       case TwoMachinesPlay:
14902         GameEnds(EndOfFile, NULL, GE_PLAYER);
14903         ResurrectChessProgram();
14904         SetUserThinkingEnables();
14905         break;
14906       case EndOfGame:
14907         ResurrectChessProgram();
14908         break;
14909       case IcsPlayingBlack:
14910       case IcsPlayingWhite:
14911         DisplayError(_("Warning: You are still playing a game"), 0);
14912         break;
14913       case IcsObserving:
14914         DisplayError(_("Warning: You are still observing a game"), 0);
14915         break;
14916       case IcsExamining:
14917         DisplayError(_("Warning: You are still examining a game"), 0);
14918         break;
14919       case IcsIdle:
14920         break;
14921       case EditGame:
14922       default:
14923         return;
14924     }
14925
14926     pausing = FALSE;
14927     StopClocks();
14928     first.offeredDraw = second.offeredDraw = 0;
14929
14930     if (gameMode == PlayFromGameFile) {
14931         whiteTimeRemaining = timeRemaining[0][currentMove];
14932         blackTimeRemaining = timeRemaining[1][currentMove];
14933         DisplayTitle("");
14934     }
14935
14936     if (gameMode == MachinePlaysWhite ||
14937         gameMode == MachinePlaysBlack ||
14938         gameMode == TwoMachinesPlay ||
14939         gameMode == EndOfGame) {
14940         i = forwardMostMove;
14941         while (i > currentMove) {
14942             SendToProgram("undo\n", &first);
14943             i--;
14944         }
14945         if(!adjustedClock) {
14946         whiteTimeRemaining = timeRemaining[0][currentMove];
14947         blackTimeRemaining = timeRemaining[1][currentMove];
14948         DisplayBothClocks();
14949         }
14950         if (whiteFlag || blackFlag) {
14951             whiteFlag = blackFlag = 0;
14952         }
14953         DisplayTitle("");
14954     }
14955
14956     gameMode = EditGame;
14957     ModeHighlight();
14958     SetGameInfo();
14959 }
14960
14961
14962 void
14963 EditPositionEvent ()
14964 {
14965     if (gameMode == EditPosition) {
14966         EditGameEvent();
14967         return;
14968     }
14969
14970     EditGameEvent();
14971     if (gameMode != EditGame) return;
14972
14973     gameMode = EditPosition;
14974     ModeHighlight();
14975     SetGameInfo();
14976     if (currentMove > 0)
14977       CopyBoard(boards[0], boards[currentMove]);
14978
14979     blackPlaysFirst = !WhiteOnMove(currentMove);
14980     ResetClocks();
14981     currentMove = forwardMostMove = backwardMostMove = 0;
14982     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14983     DisplayMove(-1);
14984     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14985 }
14986
14987 void
14988 ExitAnalyzeMode ()
14989 {
14990     /* [DM] icsEngineAnalyze - possible call from other functions */
14991     if (appData.icsEngineAnalyze) {
14992         appData.icsEngineAnalyze = FALSE;
14993
14994         DisplayMessage("",_("Close ICS engine analyze..."));
14995     }
14996     if (first.analysisSupport && first.analyzing) {
14997       SendToBoth("exit\n");
14998       first.analyzing = second.analyzing = FALSE;
14999     }
15000     thinkOutput[0] = NULLCHAR;
15001 }
15002
15003 void
15004 EditPositionDone (Boolean fakeRights)
15005 {
15006     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15007
15008     startedFromSetupPosition = TRUE;
15009     InitChessProgram(&first, FALSE);
15010     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15011       boards[0][EP_STATUS] = EP_NONE;
15012       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15013       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15014         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15015         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15016       } else boards[0][CASTLING][2] = NoRights;
15017       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15018         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15019         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15020       } else boards[0][CASTLING][5] = NoRights;
15021       if(gameInfo.variant == VariantSChess) {
15022         int i;
15023         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15024           boards[0][VIRGIN][i] = 0;
15025           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15026           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15027         }
15028       }
15029     }
15030     SendToProgram("force\n", &first);
15031     if (blackPlaysFirst) {
15032         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15033         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15034         currentMove = forwardMostMove = backwardMostMove = 1;
15035         CopyBoard(boards[1], boards[0]);
15036     } else {
15037         currentMove = forwardMostMove = backwardMostMove = 0;
15038     }
15039     SendBoard(&first, forwardMostMove);
15040     if (appData.debugMode) {
15041         fprintf(debugFP, "EditPosDone\n");
15042     }
15043     DisplayTitle("");
15044     DisplayMessage("", "");
15045     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15046     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15047     gameMode = EditGame;
15048     ModeHighlight();
15049     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15050     ClearHighlights(); /* [AS] */
15051 }
15052
15053 /* Pause for `ms' milliseconds */
15054 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15055 void
15056 TimeDelay (long ms)
15057 {
15058     TimeMark m1, m2;
15059
15060     GetTimeMark(&m1);
15061     do {
15062         GetTimeMark(&m2);
15063     } while (SubtractTimeMarks(&m2, &m1) < ms);
15064 }
15065
15066 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15067 void
15068 SendMultiLineToICS (char *buf)
15069 {
15070     char temp[MSG_SIZ+1], *p;
15071     int len;
15072
15073     len = strlen(buf);
15074     if (len > MSG_SIZ)
15075       len = MSG_SIZ;
15076
15077     strncpy(temp, buf, len);
15078     temp[len] = 0;
15079
15080     p = temp;
15081     while (*p) {
15082         if (*p == '\n' || *p == '\r')
15083           *p = ' ';
15084         ++p;
15085     }
15086
15087     strcat(temp, "\n");
15088     SendToICS(temp);
15089     SendToPlayer(temp, strlen(temp));
15090 }
15091
15092 void
15093 SetWhiteToPlayEvent ()
15094 {
15095     if (gameMode == EditPosition) {
15096         blackPlaysFirst = FALSE;
15097         DisplayBothClocks();    /* works because currentMove is 0 */
15098     } else if (gameMode == IcsExamining) {
15099         SendToICS(ics_prefix);
15100         SendToICS("tomove white\n");
15101     }
15102 }
15103
15104 void
15105 SetBlackToPlayEvent ()
15106 {
15107     if (gameMode == EditPosition) {
15108         blackPlaysFirst = TRUE;
15109         currentMove = 1;        /* kludge */
15110         DisplayBothClocks();
15111         currentMove = 0;
15112     } else if (gameMode == IcsExamining) {
15113         SendToICS(ics_prefix);
15114         SendToICS("tomove black\n");
15115     }
15116 }
15117
15118 void
15119 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15120 {
15121     char buf[MSG_SIZ];
15122     ChessSquare piece = boards[0][y][x];
15123     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15124     static int lastVariant;
15125
15126     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15127
15128     switch (selection) {
15129       case ClearBoard:
15130         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15131         MarkTargetSquares(1);
15132         CopyBoard(currentBoard, boards[0]);
15133         CopyBoard(menuBoard, initialPosition);
15134         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15135             SendToICS(ics_prefix);
15136             SendToICS("bsetup clear\n");
15137         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15138             SendToICS(ics_prefix);
15139             SendToICS("clearboard\n");
15140         } else {
15141             int nonEmpty = 0;
15142             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15143                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15144                 for (y = 0; y < BOARD_HEIGHT; y++) {
15145                     if (gameMode == IcsExamining) {
15146                         if (boards[currentMove][y][x] != EmptySquare) {
15147                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15148                                     AAA + x, ONE + y);
15149                             SendToICS(buf);
15150                         }
15151                     } else if(boards[0][y][x] != DarkSquare) {
15152                         if(boards[0][y][x] != p) nonEmpty++;
15153                         boards[0][y][x] = p;
15154                     }
15155                 }
15156             }
15157             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15158                 int r;
15159                 for(r = 0; r < BOARD_HEIGHT; r++) {
15160                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15161                     ChessSquare p = menuBoard[r][x];
15162                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15163                   }
15164                 }
15165                 DisplayMessage("Clicking clock again restores position", "");
15166                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15167                 if(!nonEmpty) { // asked to clear an empty board
15168                     CopyBoard(boards[0], menuBoard);
15169                 } else
15170                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15171                     CopyBoard(boards[0], initialPosition);
15172                 } else
15173                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15174                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15175                     CopyBoard(boards[0], erasedBoard);
15176                 } else
15177                     CopyBoard(erasedBoard, currentBoard);
15178
15179             }
15180         }
15181         if (gameMode == EditPosition) {
15182             DrawPosition(FALSE, boards[0]);
15183         }
15184         break;
15185
15186       case WhitePlay:
15187         SetWhiteToPlayEvent();
15188         break;
15189
15190       case BlackPlay:
15191         SetBlackToPlayEvent();
15192         break;
15193
15194       case EmptySquare:
15195         if (gameMode == IcsExamining) {
15196             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15197             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15198             SendToICS(buf);
15199         } else {
15200             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15201                 if(x == BOARD_LEFT-2) {
15202                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15203                     boards[0][y][1] = 0;
15204                 } else
15205                 if(x == BOARD_RGHT+1) {
15206                     if(y >= gameInfo.holdingsSize) break;
15207                     boards[0][y][BOARD_WIDTH-2] = 0;
15208                 } else break;
15209             }
15210             boards[0][y][x] = EmptySquare;
15211             DrawPosition(FALSE, boards[0]);
15212         }
15213         break;
15214
15215       case PromotePiece:
15216         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15217            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15218             selection = (ChessSquare) (PROMOTED piece);
15219         } else if(piece == EmptySquare) selection = WhiteSilver;
15220         else selection = (ChessSquare)((int)piece - 1);
15221         goto defaultlabel;
15222
15223       case DemotePiece:
15224         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15225            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15226             selection = (ChessSquare) (DEMOTED piece);
15227         } else if(piece == EmptySquare) selection = BlackSilver;
15228         else selection = (ChessSquare)((int)piece + 1);
15229         goto defaultlabel;
15230
15231       case WhiteQueen:
15232       case BlackQueen:
15233         if(gameInfo.variant == VariantShatranj ||
15234            gameInfo.variant == VariantXiangqi  ||
15235            gameInfo.variant == VariantCourier  ||
15236            gameInfo.variant == VariantASEAN    ||
15237            gameInfo.variant == VariantMakruk     )
15238             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15239         goto defaultlabel;
15240
15241       case WhiteKing:
15242       case BlackKing:
15243         if(gameInfo.variant == VariantXiangqi)
15244             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15245         if(gameInfo.variant == VariantKnightmate)
15246             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15247       default:
15248         defaultlabel:
15249         if (gameMode == IcsExamining) {
15250             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15251             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15252                      PieceToChar(selection), AAA + x, ONE + y);
15253             SendToICS(buf);
15254         } else {
15255             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15256                 int n;
15257                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15258                     n = PieceToNumber(selection - BlackPawn);
15259                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15260                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15261                     boards[0][BOARD_HEIGHT-1-n][1]++;
15262                 } else
15263                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15264                     n = PieceToNumber(selection);
15265                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15266                     boards[0][n][BOARD_WIDTH-1] = selection;
15267                     boards[0][n][BOARD_WIDTH-2]++;
15268                 }
15269             } else
15270             boards[0][y][x] = selection;
15271             DrawPosition(TRUE, boards[0]);
15272             ClearHighlights();
15273             fromX = fromY = -1;
15274         }
15275         break;
15276     }
15277 }
15278
15279
15280 void
15281 DropMenuEvent (ChessSquare selection, int x, int y)
15282 {
15283     ChessMove moveType;
15284
15285     switch (gameMode) {
15286       case IcsPlayingWhite:
15287       case MachinePlaysBlack:
15288         if (!WhiteOnMove(currentMove)) {
15289             DisplayMoveError(_("It is Black's turn"));
15290             return;
15291         }
15292         moveType = WhiteDrop;
15293         break;
15294       case IcsPlayingBlack:
15295       case MachinePlaysWhite:
15296         if (WhiteOnMove(currentMove)) {
15297             DisplayMoveError(_("It is White's turn"));
15298             return;
15299         }
15300         moveType = BlackDrop;
15301         break;
15302       case EditGame:
15303         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15304         break;
15305       default:
15306         return;
15307     }
15308
15309     if (moveType == BlackDrop && selection < BlackPawn) {
15310       selection = (ChessSquare) ((int) selection
15311                                  + (int) BlackPawn - (int) WhitePawn);
15312     }
15313     if (boards[currentMove][y][x] != EmptySquare) {
15314         DisplayMoveError(_("That square is occupied"));
15315         return;
15316     }
15317
15318     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15319 }
15320
15321 void
15322 AcceptEvent ()
15323 {
15324     /* Accept a pending offer of any kind from opponent */
15325
15326     if (appData.icsActive) {
15327         SendToICS(ics_prefix);
15328         SendToICS("accept\n");
15329     } else if (cmailMsgLoaded) {
15330         if (currentMove == cmailOldMove &&
15331             commentList[cmailOldMove] != NULL &&
15332             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15333                    "Black offers a draw" : "White offers a draw")) {
15334             TruncateGame();
15335             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15336             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15337         } else {
15338             DisplayError(_("There is no pending offer on this move"), 0);
15339             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15340         }
15341     } else {
15342         /* Not used for offers from chess program */
15343     }
15344 }
15345
15346 void
15347 DeclineEvent ()
15348 {
15349     /* Decline a pending offer of any kind from opponent */
15350
15351     if (appData.icsActive) {
15352         SendToICS(ics_prefix);
15353         SendToICS("decline\n");
15354     } else if (cmailMsgLoaded) {
15355         if (currentMove == cmailOldMove &&
15356             commentList[cmailOldMove] != NULL &&
15357             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15358                    "Black offers a draw" : "White offers a draw")) {
15359 #ifdef NOTDEF
15360             AppendComment(cmailOldMove, "Draw declined", TRUE);
15361             DisplayComment(cmailOldMove - 1, "Draw declined");
15362 #endif /*NOTDEF*/
15363         } else {
15364             DisplayError(_("There is no pending offer on this move"), 0);
15365         }
15366     } else {
15367         /* Not used for offers from chess program */
15368     }
15369 }
15370
15371 void
15372 RematchEvent ()
15373 {
15374     /* Issue ICS rematch command */
15375     if (appData.icsActive) {
15376         SendToICS(ics_prefix);
15377         SendToICS("rematch\n");
15378     }
15379 }
15380
15381 void
15382 CallFlagEvent ()
15383 {
15384     /* Call your opponent's flag (claim a win on time) */
15385     if (appData.icsActive) {
15386         SendToICS(ics_prefix);
15387         SendToICS("flag\n");
15388     } else {
15389         switch (gameMode) {
15390           default:
15391             return;
15392           case MachinePlaysWhite:
15393             if (whiteFlag) {
15394                 if (blackFlag)
15395                   GameEnds(GameIsDrawn, "Both players ran out of time",
15396                            GE_PLAYER);
15397                 else
15398                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15399             } else {
15400                 DisplayError(_("Your opponent is not out of time"), 0);
15401             }
15402             break;
15403           case MachinePlaysBlack:
15404             if (blackFlag) {
15405                 if (whiteFlag)
15406                   GameEnds(GameIsDrawn, "Both players ran out of time",
15407                            GE_PLAYER);
15408                 else
15409                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15410             } else {
15411                 DisplayError(_("Your opponent is not out of time"), 0);
15412             }
15413             break;
15414         }
15415     }
15416 }
15417
15418 void
15419 ClockClick (int which)
15420 {       // [HGM] code moved to back-end from winboard.c
15421         if(which) { // black clock
15422           if (gameMode == EditPosition || gameMode == IcsExamining) {
15423             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15424             SetBlackToPlayEvent();
15425           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15426                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15427           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15428           } else if (shiftKey) {
15429             AdjustClock(which, -1);
15430           } else if (gameMode == IcsPlayingWhite ||
15431                      gameMode == MachinePlaysBlack) {
15432             CallFlagEvent();
15433           }
15434         } else { // white clock
15435           if (gameMode == EditPosition || gameMode == IcsExamining) {
15436             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15437             SetWhiteToPlayEvent();
15438           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15439                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15440           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15441           } else if (shiftKey) {
15442             AdjustClock(which, -1);
15443           } else if (gameMode == IcsPlayingBlack ||
15444                    gameMode == MachinePlaysWhite) {
15445             CallFlagEvent();
15446           }
15447         }
15448 }
15449
15450 void
15451 DrawEvent ()
15452 {
15453     /* Offer draw or accept pending draw offer from opponent */
15454
15455     if (appData.icsActive) {
15456         /* Note: tournament rules require draw offers to be
15457            made after you make your move but before you punch
15458            your clock.  Currently ICS doesn't let you do that;
15459            instead, you immediately punch your clock after making
15460            a move, but you can offer a draw at any time. */
15461
15462         SendToICS(ics_prefix);
15463         SendToICS("draw\n");
15464         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15465     } else if (cmailMsgLoaded) {
15466         if (currentMove == cmailOldMove &&
15467             commentList[cmailOldMove] != NULL &&
15468             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15469                    "Black offers a draw" : "White offers a draw")) {
15470             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15471             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15472         } else if (currentMove == cmailOldMove + 1) {
15473             char *offer = WhiteOnMove(cmailOldMove) ?
15474               "White offers a draw" : "Black offers a draw";
15475             AppendComment(currentMove, offer, TRUE);
15476             DisplayComment(currentMove - 1, offer);
15477             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15478         } else {
15479             DisplayError(_("You must make your move before offering a draw"), 0);
15480             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15481         }
15482     } else if (first.offeredDraw) {
15483         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15484     } else {
15485         if (first.sendDrawOffers) {
15486             SendToProgram("draw\n", &first);
15487             userOfferedDraw = TRUE;
15488         }
15489     }
15490 }
15491
15492 void
15493 AdjournEvent ()
15494 {
15495     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15496
15497     if (appData.icsActive) {
15498         SendToICS(ics_prefix);
15499         SendToICS("adjourn\n");
15500     } else {
15501         /* Currently GNU Chess doesn't offer or accept Adjourns */
15502     }
15503 }
15504
15505
15506 void
15507 AbortEvent ()
15508 {
15509     /* Offer Abort or accept pending Abort offer from opponent */
15510
15511     if (appData.icsActive) {
15512         SendToICS(ics_prefix);
15513         SendToICS("abort\n");
15514     } else {
15515         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15516     }
15517 }
15518
15519 void
15520 ResignEvent ()
15521 {
15522     /* Resign.  You can do this even if it's not your turn. */
15523
15524     if (appData.icsActive) {
15525         SendToICS(ics_prefix);
15526         SendToICS("resign\n");
15527     } else {
15528         switch (gameMode) {
15529           case MachinePlaysWhite:
15530             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15531             break;
15532           case MachinePlaysBlack:
15533             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15534             break;
15535           case EditGame:
15536             if (cmailMsgLoaded) {
15537                 TruncateGame();
15538                 if (WhiteOnMove(cmailOldMove)) {
15539                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15540                 } else {
15541                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15542                 }
15543                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15544             }
15545             break;
15546           default:
15547             break;
15548         }
15549     }
15550 }
15551
15552
15553 void
15554 StopObservingEvent ()
15555 {
15556     /* Stop observing current games */
15557     SendToICS(ics_prefix);
15558     SendToICS("unobserve\n");
15559 }
15560
15561 void
15562 StopExaminingEvent ()
15563 {
15564     /* Stop observing current game */
15565     SendToICS(ics_prefix);
15566     SendToICS("unexamine\n");
15567 }
15568
15569 void
15570 ForwardInner (int target)
15571 {
15572     int limit; int oldSeekGraphUp = seekGraphUp;
15573
15574     if (appData.debugMode)
15575         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15576                 target, currentMove, forwardMostMove);
15577
15578     if (gameMode == EditPosition)
15579       return;
15580
15581     seekGraphUp = FALSE;
15582     MarkTargetSquares(1);
15583     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15584
15585     if (gameMode == PlayFromGameFile && !pausing)
15586       PauseEvent();
15587
15588     if (gameMode == IcsExamining && pausing)
15589       limit = pauseExamForwardMostMove;
15590     else
15591       limit = forwardMostMove;
15592
15593     if (target > limit) target = limit;
15594
15595     if (target > 0 && moveList[target - 1][0]) {
15596         int fromX, fromY, toX, toY;
15597         toX = moveList[target - 1][2] - AAA;
15598         toY = moveList[target - 1][3] - ONE;
15599         if (moveList[target - 1][1] == '@') {
15600             if (appData.highlightLastMove) {
15601                 SetHighlights(-1, -1, toX, toY);
15602             }
15603         } else {
15604             int viaX = moveList[target - 1][5] - AAA;
15605             int viaY = moveList[target - 1][6] - ONE;
15606             fromX = moveList[target - 1][0] - AAA;
15607             fromY = moveList[target - 1][1] - ONE;
15608             if (target == currentMove + 1) {
15609                 if(moveList[target - 1][4] == ';') { // multi-leg
15610                     ChessSquare piece = boards[currentMove][viaY][viaX];
15611                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15612                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15613                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15614                     boards[currentMove][viaY][viaX] = piece;
15615                 } else
15616                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15617             }
15618             if (appData.highlightLastMove) {
15619                 SetHighlights(fromX, fromY, toX, toY);
15620             }
15621         }
15622     }
15623     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15624         gameMode == Training || gameMode == PlayFromGameFile ||
15625         gameMode == AnalyzeFile) {
15626         while (currentMove < target) {
15627             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15628             SendMoveToProgram(currentMove++, &first);
15629         }
15630     } else {
15631         currentMove = target;
15632     }
15633
15634     if (gameMode == EditGame || gameMode == EndOfGame) {
15635         whiteTimeRemaining = timeRemaining[0][currentMove];
15636         blackTimeRemaining = timeRemaining[1][currentMove];
15637     }
15638     DisplayBothClocks();
15639     DisplayMove(currentMove - 1);
15640     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15641     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15642     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15643         DisplayComment(currentMove - 1, commentList[currentMove]);
15644     }
15645     ClearMap(); // [HGM] exclude: invalidate map
15646 }
15647
15648
15649 void
15650 ForwardEvent ()
15651 {
15652     if (gameMode == IcsExamining && !pausing) {
15653         SendToICS(ics_prefix);
15654         SendToICS("forward\n");
15655     } else {
15656         ForwardInner(currentMove + 1);
15657     }
15658 }
15659
15660 void
15661 ToEndEvent ()
15662 {
15663     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15664         /* to optimze, we temporarily turn off analysis mode while we feed
15665          * the remaining moves to the engine. Otherwise we get analysis output
15666          * after each move.
15667          */
15668         if (first.analysisSupport) {
15669           SendToProgram("exit\nforce\n", &first);
15670           first.analyzing = FALSE;
15671         }
15672     }
15673
15674     if (gameMode == IcsExamining && !pausing) {
15675         SendToICS(ics_prefix);
15676         SendToICS("forward 999999\n");
15677     } else {
15678         ForwardInner(forwardMostMove);
15679     }
15680
15681     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15682         /* we have fed all the moves, so reactivate analysis mode */
15683         SendToProgram("analyze\n", &first);
15684         first.analyzing = TRUE;
15685         /*first.maybeThinking = TRUE;*/
15686         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15687     }
15688 }
15689
15690 void
15691 BackwardInner (int target)
15692 {
15693     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15694
15695     if (appData.debugMode)
15696         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15697                 target, currentMove, forwardMostMove);
15698
15699     if (gameMode == EditPosition) return;
15700     seekGraphUp = FALSE;
15701     MarkTargetSquares(1);
15702     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15703     if (currentMove <= backwardMostMove) {
15704         ClearHighlights();
15705         DrawPosition(full_redraw, boards[currentMove]);
15706         return;
15707     }
15708     if (gameMode == PlayFromGameFile && !pausing)
15709       PauseEvent();
15710
15711     if (moveList[target][0]) {
15712         int fromX, fromY, toX, toY;
15713         toX = moveList[target][2] - AAA;
15714         toY = moveList[target][3] - ONE;
15715         if (moveList[target][1] == '@') {
15716             if (appData.highlightLastMove) {
15717                 SetHighlights(-1, -1, toX, toY);
15718             }
15719         } else {
15720             fromX = moveList[target][0] - AAA;
15721             fromY = moveList[target][1] - ONE;
15722             if (target == currentMove - 1) {
15723                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15724             }
15725             if (appData.highlightLastMove) {
15726                 SetHighlights(fromX, fromY, toX, toY);
15727             }
15728         }
15729     }
15730     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15731         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15732         while (currentMove > target) {
15733             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15734                 // null move cannot be undone. Reload program with move history before it.
15735                 int i;
15736                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15737                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15738                 }
15739                 SendBoard(&first, i);
15740               if(second.analyzing) SendBoard(&second, i);
15741                 for(currentMove=i; currentMove<target; currentMove++) {
15742                     SendMoveToProgram(currentMove, &first);
15743                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15744                 }
15745                 break;
15746             }
15747             SendToBoth("undo\n");
15748             currentMove--;
15749         }
15750     } else {
15751         currentMove = target;
15752     }
15753
15754     if (gameMode == EditGame || gameMode == EndOfGame) {
15755         whiteTimeRemaining = timeRemaining[0][currentMove];
15756         blackTimeRemaining = timeRemaining[1][currentMove];
15757     }
15758     DisplayBothClocks();
15759     DisplayMove(currentMove - 1);
15760     DrawPosition(full_redraw, boards[currentMove]);
15761     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15762     // [HGM] PV info: routine tests if comment empty
15763     DisplayComment(currentMove - 1, commentList[currentMove]);
15764     ClearMap(); // [HGM] exclude: invalidate map
15765 }
15766
15767 void
15768 BackwardEvent ()
15769 {
15770     if (gameMode == IcsExamining && !pausing) {
15771         SendToICS(ics_prefix);
15772         SendToICS("backward\n");
15773     } else {
15774         BackwardInner(currentMove - 1);
15775     }
15776 }
15777
15778 void
15779 ToStartEvent ()
15780 {
15781     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15782         /* to optimize, we temporarily turn off analysis mode while we undo
15783          * all the moves. Otherwise we get analysis output after each undo.
15784          */
15785         if (first.analysisSupport) {
15786           SendToProgram("exit\nforce\n", &first);
15787           first.analyzing = FALSE;
15788         }
15789     }
15790
15791     if (gameMode == IcsExamining && !pausing) {
15792         SendToICS(ics_prefix);
15793         SendToICS("backward 999999\n");
15794     } else {
15795         BackwardInner(backwardMostMove);
15796     }
15797
15798     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15799         /* we have fed all the moves, so reactivate analysis mode */
15800         SendToProgram("analyze\n", &first);
15801         first.analyzing = TRUE;
15802         /*first.maybeThinking = TRUE;*/
15803         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15804     }
15805 }
15806
15807 void
15808 ToNrEvent (int to)
15809 {
15810   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15811   if (to >= forwardMostMove) to = forwardMostMove;
15812   if (to <= backwardMostMove) to = backwardMostMove;
15813   if (to < currentMove) {
15814     BackwardInner(to);
15815   } else {
15816     ForwardInner(to);
15817   }
15818 }
15819
15820 void
15821 RevertEvent (Boolean annotate)
15822 {
15823     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15824         return;
15825     }
15826     if (gameMode != IcsExamining) {
15827         DisplayError(_("You are not examining a game"), 0);
15828         return;
15829     }
15830     if (pausing) {
15831         DisplayError(_("You can't revert while pausing"), 0);
15832         return;
15833     }
15834     SendToICS(ics_prefix);
15835     SendToICS("revert\n");
15836 }
15837
15838 void
15839 RetractMoveEvent ()
15840 {
15841     switch (gameMode) {
15842       case MachinePlaysWhite:
15843       case MachinePlaysBlack:
15844         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15845             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15846             return;
15847         }
15848         if (forwardMostMove < 2) return;
15849         currentMove = forwardMostMove = forwardMostMove - 2;
15850         whiteTimeRemaining = timeRemaining[0][currentMove];
15851         blackTimeRemaining = timeRemaining[1][currentMove];
15852         DisplayBothClocks();
15853         DisplayMove(currentMove - 1);
15854         ClearHighlights();/*!! could figure this out*/
15855         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15856         SendToProgram("remove\n", &first);
15857         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15858         break;
15859
15860       case BeginningOfGame:
15861       default:
15862         break;
15863
15864       case IcsPlayingWhite:
15865       case IcsPlayingBlack:
15866         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15867             SendToICS(ics_prefix);
15868             SendToICS("takeback 2\n");
15869         } else {
15870             SendToICS(ics_prefix);
15871             SendToICS("takeback 1\n");
15872         }
15873         break;
15874     }
15875 }
15876
15877 void
15878 MoveNowEvent ()
15879 {
15880     ChessProgramState *cps;
15881
15882     switch (gameMode) {
15883       case MachinePlaysWhite:
15884         if (!WhiteOnMove(forwardMostMove)) {
15885             DisplayError(_("It is your turn"), 0);
15886             return;
15887         }
15888         cps = &first;
15889         break;
15890       case MachinePlaysBlack:
15891         if (WhiteOnMove(forwardMostMove)) {
15892             DisplayError(_("It is your turn"), 0);
15893             return;
15894         }
15895         cps = &first;
15896         break;
15897       case TwoMachinesPlay:
15898         if (WhiteOnMove(forwardMostMove) ==
15899             (first.twoMachinesColor[0] == 'w')) {
15900             cps = &first;
15901         } else {
15902             cps = &second;
15903         }
15904         break;
15905       case BeginningOfGame:
15906       default:
15907         return;
15908     }
15909     SendToProgram("?\n", cps);
15910 }
15911
15912 void
15913 TruncateGameEvent ()
15914 {
15915     EditGameEvent();
15916     if (gameMode != EditGame) return;
15917     TruncateGame();
15918 }
15919
15920 void
15921 TruncateGame ()
15922 {
15923     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15924     if (forwardMostMove > currentMove) {
15925         if (gameInfo.resultDetails != NULL) {
15926             free(gameInfo.resultDetails);
15927             gameInfo.resultDetails = NULL;
15928             gameInfo.result = GameUnfinished;
15929         }
15930         forwardMostMove = currentMove;
15931         HistorySet(parseList, backwardMostMove, forwardMostMove,
15932                    currentMove-1);
15933     }
15934 }
15935
15936 void
15937 HintEvent ()
15938 {
15939     if (appData.noChessProgram) return;
15940     switch (gameMode) {
15941       case MachinePlaysWhite:
15942         if (WhiteOnMove(forwardMostMove)) {
15943             DisplayError(_("Wait until your turn."), 0);
15944             return;
15945         }
15946         break;
15947       case BeginningOfGame:
15948       case MachinePlaysBlack:
15949         if (!WhiteOnMove(forwardMostMove)) {
15950             DisplayError(_("Wait until your turn."), 0);
15951             return;
15952         }
15953         break;
15954       default:
15955         DisplayError(_("No hint available"), 0);
15956         return;
15957     }
15958     SendToProgram("hint\n", &first);
15959     hintRequested = TRUE;
15960 }
15961
15962 int
15963 SaveSelected (FILE *g, int dummy, char *dummy2)
15964 {
15965     ListGame * lg = (ListGame *) gameList.head;
15966     int nItem, cnt=0;
15967     FILE *f;
15968
15969     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15970         DisplayError(_("Game list not loaded or empty"), 0);
15971         return 0;
15972     }
15973
15974     creatingBook = TRUE; // suppresses stuff during load game
15975
15976     /* Get list size */
15977     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15978         if(lg->position >= 0) { // selected?
15979             LoadGame(f, nItem, "", TRUE);
15980             SaveGamePGN2(g); // leaves g open
15981             cnt++; DoEvents();
15982         }
15983         lg = (ListGame *) lg->node.succ;
15984     }
15985
15986     fclose(g);
15987     creatingBook = FALSE;
15988
15989     return cnt;
15990 }
15991
15992 void
15993 CreateBookEvent ()
15994 {
15995     ListGame * lg = (ListGame *) gameList.head;
15996     FILE *f, *g;
15997     int nItem;
15998     static int secondTime = FALSE;
15999
16000     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16001         DisplayError(_("Game list not loaded or empty"), 0);
16002         return;
16003     }
16004
16005     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16006         fclose(g);
16007         secondTime++;
16008         DisplayNote(_("Book file exists! Try again for overwrite."));
16009         return;
16010     }
16011
16012     creatingBook = TRUE;
16013     secondTime = FALSE;
16014
16015     /* Get list size */
16016     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16017         if(lg->position >= 0) {
16018             LoadGame(f, nItem, "", TRUE);
16019             AddGameToBook(TRUE);
16020             DoEvents();
16021         }
16022         lg = (ListGame *) lg->node.succ;
16023     }
16024
16025     creatingBook = FALSE;
16026     FlushBook();
16027 }
16028
16029 void
16030 BookEvent ()
16031 {
16032     if (appData.noChessProgram) return;
16033     switch (gameMode) {
16034       case MachinePlaysWhite:
16035         if (WhiteOnMove(forwardMostMove)) {
16036             DisplayError(_("Wait until your turn."), 0);
16037             return;
16038         }
16039         break;
16040       case BeginningOfGame:
16041       case MachinePlaysBlack:
16042         if (!WhiteOnMove(forwardMostMove)) {
16043             DisplayError(_("Wait until your turn."), 0);
16044             return;
16045         }
16046         break;
16047       case EditPosition:
16048         EditPositionDone(TRUE);
16049         break;
16050       case TwoMachinesPlay:
16051         return;
16052       default:
16053         break;
16054     }
16055     SendToProgram("bk\n", &first);
16056     bookOutput[0] = NULLCHAR;
16057     bookRequested = TRUE;
16058 }
16059
16060 void
16061 AboutGameEvent ()
16062 {
16063     char *tags = PGNTags(&gameInfo);
16064     TagsPopUp(tags, CmailMsg());
16065     free(tags);
16066 }
16067
16068 /* end button procedures */
16069
16070 void
16071 PrintPosition (FILE *fp, int move)
16072 {
16073     int i, j;
16074
16075     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16076         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16077             char c = PieceToChar(boards[move][i][j]);
16078             fputc(c == 'x' ? '.' : c, fp);
16079             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16080         }
16081     }
16082     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16083       fprintf(fp, "white to play\n");
16084     else
16085       fprintf(fp, "black to play\n");
16086 }
16087
16088 void
16089 PrintOpponents (FILE *fp)
16090 {
16091     if (gameInfo.white != NULL) {
16092         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16093     } else {
16094         fprintf(fp, "\n");
16095     }
16096 }
16097
16098 /* Find last component of program's own name, using some heuristics */
16099 void
16100 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16101 {
16102     char *p, *q, c;
16103     int local = (strcmp(host, "localhost") == 0);
16104     while (!local && (p = strchr(prog, ';')) != NULL) {
16105         p++;
16106         while (*p == ' ') p++;
16107         prog = p;
16108     }
16109     if (*prog == '"' || *prog == '\'') {
16110         q = strchr(prog + 1, *prog);
16111     } else {
16112         q = strchr(prog, ' ');
16113     }
16114     if (q == NULL) q = prog + strlen(prog);
16115     p = q;
16116     while (p >= prog && *p != '/' && *p != '\\') p--;
16117     p++;
16118     if(p == prog && *p == '"') p++;
16119     c = *q; *q = 0;
16120     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16121     memcpy(buf, p, q - p);
16122     buf[q - p] = NULLCHAR;
16123     if (!local) {
16124         strcat(buf, "@");
16125         strcat(buf, host);
16126     }
16127 }
16128
16129 char *
16130 TimeControlTagValue ()
16131 {
16132     char buf[MSG_SIZ];
16133     if (!appData.clockMode) {
16134       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16135     } else if (movesPerSession > 0) {
16136       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16137     } else if (timeIncrement == 0) {
16138       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16139     } else {
16140       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16141     }
16142     return StrSave(buf);
16143 }
16144
16145 void
16146 SetGameInfo ()
16147 {
16148     /* This routine is used only for certain modes */
16149     VariantClass v = gameInfo.variant;
16150     ChessMove r = GameUnfinished;
16151     char *p = NULL;
16152
16153     if(keepInfo) return;
16154
16155     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16156         r = gameInfo.result;
16157         p = gameInfo.resultDetails;
16158         gameInfo.resultDetails = NULL;
16159     }
16160     ClearGameInfo(&gameInfo);
16161     gameInfo.variant = v;
16162
16163     switch (gameMode) {
16164       case MachinePlaysWhite:
16165         gameInfo.event = StrSave( appData.pgnEventHeader );
16166         gameInfo.site = StrSave(HostName());
16167         gameInfo.date = PGNDate();
16168         gameInfo.round = StrSave("-");
16169         gameInfo.white = StrSave(first.tidy);
16170         gameInfo.black = StrSave(UserName());
16171         gameInfo.timeControl = TimeControlTagValue();
16172         break;
16173
16174       case MachinePlaysBlack:
16175         gameInfo.event = StrSave( appData.pgnEventHeader );
16176         gameInfo.site = StrSave(HostName());
16177         gameInfo.date = PGNDate();
16178         gameInfo.round = StrSave("-");
16179         gameInfo.white = StrSave(UserName());
16180         gameInfo.black = StrSave(first.tidy);
16181         gameInfo.timeControl = TimeControlTagValue();
16182         break;
16183
16184       case TwoMachinesPlay:
16185         gameInfo.event = StrSave( appData.pgnEventHeader );
16186         gameInfo.site = StrSave(HostName());
16187         gameInfo.date = PGNDate();
16188         if (roundNr > 0) {
16189             char buf[MSG_SIZ];
16190             snprintf(buf, MSG_SIZ, "%d", roundNr);
16191             gameInfo.round = StrSave(buf);
16192         } else {
16193             gameInfo.round = StrSave("-");
16194         }
16195         if (first.twoMachinesColor[0] == 'w') {
16196             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16197             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16198         } else {
16199             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16200             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16201         }
16202         gameInfo.timeControl = TimeControlTagValue();
16203         break;
16204
16205       case EditGame:
16206         gameInfo.event = StrSave("Edited game");
16207         gameInfo.site = StrSave(HostName());
16208         gameInfo.date = PGNDate();
16209         gameInfo.round = StrSave("-");
16210         gameInfo.white = StrSave("-");
16211         gameInfo.black = StrSave("-");
16212         gameInfo.result = r;
16213         gameInfo.resultDetails = p;
16214         break;
16215
16216       case EditPosition:
16217         gameInfo.event = StrSave("Edited position");
16218         gameInfo.site = StrSave(HostName());
16219         gameInfo.date = PGNDate();
16220         gameInfo.round = StrSave("-");
16221         gameInfo.white = StrSave("-");
16222         gameInfo.black = StrSave("-");
16223         break;
16224
16225       case IcsPlayingWhite:
16226       case IcsPlayingBlack:
16227       case IcsObserving:
16228       case IcsExamining:
16229         break;
16230
16231       case PlayFromGameFile:
16232         gameInfo.event = StrSave("Game from non-PGN file");
16233         gameInfo.site = StrSave(HostName());
16234         gameInfo.date = PGNDate();
16235         gameInfo.round = StrSave("-");
16236         gameInfo.white = StrSave("?");
16237         gameInfo.black = StrSave("?");
16238         break;
16239
16240       default:
16241         break;
16242     }
16243 }
16244
16245 void
16246 ReplaceComment (int index, char *text)
16247 {
16248     int len;
16249     char *p;
16250     float score;
16251
16252     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16253        pvInfoList[index-1].depth == len &&
16254        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16255        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16256     while (*text == '\n') text++;
16257     len = strlen(text);
16258     while (len > 0 && text[len - 1] == '\n') len--;
16259
16260     if (commentList[index] != NULL)
16261       free(commentList[index]);
16262
16263     if (len == 0) {
16264         commentList[index] = NULL;
16265         return;
16266     }
16267   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16268       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16269       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16270     commentList[index] = (char *) malloc(len + 2);
16271     strncpy(commentList[index], text, len);
16272     commentList[index][len] = '\n';
16273     commentList[index][len + 1] = NULLCHAR;
16274   } else {
16275     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16276     char *p;
16277     commentList[index] = (char *) malloc(len + 7);
16278     safeStrCpy(commentList[index], "{\n", 3);
16279     safeStrCpy(commentList[index]+2, text, len+1);
16280     commentList[index][len+2] = NULLCHAR;
16281     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16282     strcat(commentList[index], "\n}\n");
16283   }
16284 }
16285
16286 void
16287 CrushCRs (char *text)
16288 {
16289   char *p = text;
16290   char *q = text;
16291   char ch;
16292
16293   do {
16294     ch = *p++;
16295     if (ch == '\r') continue;
16296     *q++ = ch;
16297   } while (ch != '\0');
16298 }
16299
16300 void
16301 AppendComment (int index, char *text, Boolean addBraces)
16302 /* addBraces  tells if we should add {} */
16303 {
16304     int oldlen, len;
16305     char *old;
16306
16307 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16308     if(addBraces == 3) addBraces = 0; else // force appending literally
16309     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16310
16311     CrushCRs(text);
16312     while (*text == '\n') text++;
16313     len = strlen(text);
16314     while (len > 0 && text[len - 1] == '\n') len--;
16315     text[len] = NULLCHAR;
16316
16317     if (len == 0) return;
16318
16319     if (commentList[index] != NULL) {
16320       Boolean addClosingBrace = addBraces;
16321         old = commentList[index];
16322         oldlen = strlen(old);
16323         while(commentList[index][oldlen-1] ==  '\n')
16324           commentList[index][--oldlen] = NULLCHAR;
16325         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16326         safeStrCpy(commentList[index], old, oldlen + len + 6);
16327         free(old);
16328         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16329         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16330           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16331           while (*text == '\n') { text++; len--; }
16332           commentList[index][--oldlen] = NULLCHAR;
16333       }
16334         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16335         else          strcat(commentList[index], "\n");
16336         strcat(commentList[index], text);
16337         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16338         else          strcat(commentList[index], "\n");
16339     } else {
16340         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16341         if(addBraces)
16342           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16343         else commentList[index][0] = NULLCHAR;
16344         strcat(commentList[index], text);
16345         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16346         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16347     }
16348 }
16349
16350 static char *
16351 FindStr (char * text, char * sub_text)
16352 {
16353     char * result = strstr( text, sub_text );
16354
16355     if( result != NULL ) {
16356         result += strlen( sub_text );
16357     }
16358
16359     return result;
16360 }
16361
16362 /* [AS] Try to extract PV info from PGN comment */
16363 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16364 char *
16365 GetInfoFromComment (int index, char * text)
16366 {
16367     char * sep = text, *p;
16368
16369     if( text != NULL && index > 0 ) {
16370         int score = 0;
16371         int depth = 0;
16372         int time = -1, sec = 0, deci;
16373         char * s_eval = FindStr( text, "[%eval " );
16374         char * s_emt = FindStr( text, "[%emt " );
16375 #if 0
16376         if( s_eval != NULL || s_emt != NULL ) {
16377 #else
16378         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16379 #endif
16380             /* New style */
16381             char delim;
16382
16383             if( s_eval != NULL ) {
16384                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16385                     return text;
16386                 }
16387
16388                 if( delim != ']' ) {
16389                     return text;
16390                 }
16391             }
16392
16393             if( s_emt != NULL ) {
16394             }
16395                 return text;
16396         }
16397         else {
16398             /* We expect something like: [+|-]nnn.nn/dd */
16399             int score_lo = 0;
16400
16401             if(*text != '{') return text; // [HGM] braces: must be normal comment
16402
16403             sep = strchr( text, '/' );
16404             if( sep == NULL || sep < (text+4) ) {
16405                 return text;
16406             }
16407
16408             p = text;
16409             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16410             if(p[1] == '(') { // comment starts with PV
16411                p = strchr(p, ')'); // locate end of PV
16412                if(p == NULL || sep < p+5) return text;
16413                // at this point we have something like "{(.*) +0.23/6 ..."
16414                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16415                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16416                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16417             }
16418             time = -1; sec = -1; deci = -1;
16419             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16420                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16421                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16422                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16423                 return text;
16424             }
16425
16426             if( score_lo < 0 || score_lo >= 100 ) {
16427                 return text;
16428             }
16429
16430             if(sec >= 0) time = 600*time + 10*sec; else
16431             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16432
16433             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16434
16435             /* [HGM] PV time: now locate end of PV info */
16436             while( *++sep >= '0' && *sep <= '9'); // strip depth
16437             if(time >= 0)
16438             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16439             if(sec >= 0)
16440             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16441             if(deci >= 0)
16442             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16443             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16444         }
16445
16446         if( depth <= 0 ) {
16447             return text;
16448         }
16449
16450         if( time < 0 ) {
16451             time = -1;
16452         }
16453
16454         pvInfoList[index-1].depth = depth;
16455         pvInfoList[index-1].score = score;
16456         pvInfoList[index-1].time  = 10*time; // centi-sec
16457         if(*sep == '}') *sep = 0; else *--sep = '{';
16458         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16459     }
16460     return sep;
16461 }
16462
16463 void
16464 SendToProgram (char *message, ChessProgramState *cps)
16465 {
16466     int count, outCount, error;
16467     char buf[MSG_SIZ];
16468
16469     if (cps->pr == NoProc) return;
16470     Attention(cps);
16471
16472     if (appData.debugMode) {
16473         TimeMark now;
16474         GetTimeMark(&now);
16475         fprintf(debugFP, "%ld >%-6s: %s",
16476                 SubtractTimeMarks(&now, &programStartTime),
16477                 cps->which, message);
16478         if(serverFP)
16479             fprintf(serverFP, "%ld >%-6s: %s",
16480                 SubtractTimeMarks(&now, &programStartTime),
16481                 cps->which, message), fflush(serverFP);
16482     }
16483
16484     count = strlen(message);
16485     outCount = OutputToProcess(cps->pr, message, count, &error);
16486     if (outCount < count && !exiting
16487                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16488       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16489       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16490         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16491             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16492                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16493                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16494                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16495             } else {
16496                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16497                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16498                 gameInfo.result = res;
16499             }
16500             gameInfo.resultDetails = StrSave(buf);
16501         }
16502         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16503         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16504     }
16505 }
16506
16507 void
16508 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16509 {
16510     char *end_str;
16511     char buf[MSG_SIZ];
16512     ChessProgramState *cps = (ChessProgramState *)closure;
16513
16514     if (isr != cps->isr) return; /* Killed intentionally */
16515     if (count <= 0) {
16516         if (count == 0) {
16517             RemoveInputSource(cps->isr);
16518             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16519                     _(cps->which), cps->program);
16520             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16521             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16522                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16523                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16524                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16525                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16526                 } else {
16527                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16528                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16529                     gameInfo.result = res;
16530                 }
16531                 gameInfo.resultDetails = StrSave(buf);
16532             }
16533             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16534             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16535         } else {
16536             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16537                     _(cps->which), cps->program);
16538             RemoveInputSource(cps->isr);
16539
16540             /* [AS] Program is misbehaving badly... kill it */
16541             if( count == -2 ) {
16542                 DestroyChildProcess( cps->pr, 9 );
16543                 cps->pr = NoProc;
16544             }
16545
16546             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16547         }
16548         return;
16549     }
16550
16551     if ((end_str = strchr(message, '\r')) != NULL)
16552       *end_str = NULLCHAR;
16553     if ((end_str = strchr(message, '\n')) != NULL)
16554       *end_str = NULLCHAR;
16555
16556     if (appData.debugMode) {
16557         TimeMark now; int print = 1;
16558         char *quote = ""; char c; int i;
16559
16560         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16561                 char start = message[0];
16562                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16563                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16564                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16565                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16566                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16567                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16568                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16569                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16570                    sscanf(message, "hint: %c", &c)!=1 &&
16571                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16572                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16573                     print = (appData.engineComments >= 2);
16574                 }
16575                 message[0] = start; // restore original message
16576         }
16577         if(print) {
16578                 GetTimeMark(&now);
16579                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16580                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16581                         quote,
16582                         message);
16583                 if(serverFP)
16584                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16585                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16586                         quote,
16587                         message), fflush(serverFP);
16588         }
16589     }
16590
16591     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16592     if (appData.icsEngineAnalyze) {
16593         if (strstr(message, "whisper") != NULL ||
16594              strstr(message, "kibitz") != NULL ||
16595             strstr(message, "tellics") != NULL) return;
16596     }
16597
16598     HandleMachineMove(message, cps);
16599 }
16600
16601
16602 void
16603 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16604 {
16605     char buf[MSG_SIZ];
16606     int seconds;
16607
16608     if( timeControl_2 > 0 ) {
16609         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16610             tc = timeControl_2;
16611         }
16612     }
16613     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16614     inc /= cps->timeOdds;
16615     st  /= cps->timeOdds;
16616
16617     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16618
16619     if (st > 0) {
16620       /* Set exact time per move, normally using st command */
16621       if (cps->stKludge) {
16622         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16623         seconds = st % 60;
16624         if (seconds == 0) {
16625           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16626         } else {
16627           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16628         }
16629       } else {
16630         snprintf(buf, MSG_SIZ, "st %d\n", st);
16631       }
16632     } else {
16633       /* Set conventional or incremental time control, using level command */
16634       if (seconds == 0) {
16635         /* Note old gnuchess bug -- minutes:seconds used to not work.
16636            Fixed in later versions, but still avoid :seconds
16637            when seconds is 0. */
16638         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16639       } else {
16640         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16641                  seconds, inc/1000.);
16642       }
16643     }
16644     SendToProgram(buf, cps);
16645
16646     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16647     /* Orthogonally, limit search to given depth */
16648     if (sd > 0) {
16649       if (cps->sdKludge) {
16650         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16651       } else {
16652         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16653       }
16654       SendToProgram(buf, cps);
16655     }
16656
16657     if(cps->nps >= 0) { /* [HGM] nps */
16658         if(cps->supportsNPS == FALSE)
16659           cps->nps = -1; // don't use if engine explicitly says not supported!
16660         else {
16661           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16662           SendToProgram(buf, cps);
16663         }
16664     }
16665 }
16666
16667 ChessProgramState *
16668 WhitePlayer ()
16669 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16670 {
16671     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16672        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16673         return &second;
16674     return &first;
16675 }
16676
16677 void
16678 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16679 {
16680     char message[MSG_SIZ];
16681     long time, otime;
16682
16683     /* Note: this routine must be called when the clocks are stopped
16684        or when they have *just* been set or switched; otherwise
16685        it will be off by the time since the current tick started.
16686     */
16687     if (machineWhite) {
16688         time = whiteTimeRemaining / 10;
16689         otime = blackTimeRemaining / 10;
16690     } else {
16691         time = blackTimeRemaining / 10;
16692         otime = whiteTimeRemaining / 10;
16693     }
16694     /* [HGM] translate opponent's time by time-odds factor */
16695     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16696
16697     if (time <= 0) time = 1;
16698     if (otime <= 0) otime = 1;
16699
16700     snprintf(message, MSG_SIZ, "time %ld\n", time);
16701     SendToProgram(message, cps);
16702
16703     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16704     SendToProgram(message, cps);
16705 }
16706
16707 char *
16708 EngineDefinedVariant (ChessProgramState *cps, int n)
16709 {   // return name of n-th unknown variant that engine supports
16710     static char buf[MSG_SIZ];
16711     char *p, *s = cps->variants;
16712     if(!s) return NULL;
16713     do { // parse string from variants feature
16714       VariantClass v;
16715         p = strchr(s, ',');
16716         if(p) *p = NULLCHAR;
16717       v = StringToVariant(s);
16718       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16719         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16720             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16721                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16722                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16723                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16724             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16725         }
16726         if(p) *p++ = ',';
16727         if(n < 0) return buf;
16728     } while(s = p);
16729     return NULL;
16730 }
16731
16732 int
16733 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16734 {
16735   char buf[MSG_SIZ];
16736   int len = strlen(name);
16737   int val;
16738
16739   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16740     (*p) += len + 1;
16741     sscanf(*p, "%d", &val);
16742     *loc = (val != 0);
16743     while (**p && **p != ' ')
16744       (*p)++;
16745     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16746     SendToProgram(buf, cps);
16747     return TRUE;
16748   }
16749   return FALSE;
16750 }
16751
16752 int
16753 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16754 {
16755   char buf[MSG_SIZ];
16756   int len = strlen(name);
16757   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16758     (*p) += len + 1;
16759     sscanf(*p, "%d", loc);
16760     while (**p && **p != ' ') (*p)++;
16761     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16762     SendToProgram(buf, cps);
16763     return TRUE;
16764   }
16765   return FALSE;
16766 }
16767
16768 int
16769 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16770 {
16771   char buf[MSG_SIZ];
16772   int len = strlen(name);
16773   if (strncmp((*p), name, len) == 0
16774       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16775     (*p) += len + 2;
16776     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16777     sscanf(*p, "%[^\"]", *loc);
16778     while (**p && **p != '\"') (*p)++;
16779     if (**p == '\"') (*p)++;
16780     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16781     SendToProgram(buf, cps);
16782     return TRUE;
16783   }
16784   return FALSE;
16785 }
16786
16787 int
16788 ParseOption (Option *opt, ChessProgramState *cps)
16789 // [HGM] options: process the string that defines an engine option, and determine
16790 // name, type, default value, and allowed value range
16791 {
16792         char *p, *q, buf[MSG_SIZ];
16793         int n, min = (-1)<<31, max = 1<<31, def;
16794
16795         if(p = strstr(opt->name, " -spin ")) {
16796             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16797             if(max < min) max = min; // enforce consistency
16798             if(def < min) def = min;
16799             if(def > max) def = max;
16800             opt->value = def;
16801             opt->min = min;
16802             opt->max = max;
16803             opt->type = Spin;
16804         } else if((p = strstr(opt->name, " -slider "))) {
16805             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16806             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16807             if(max < min) max = min; // enforce consistency
16808             if(def < min) def = min;
16809             if(def > max) def = max;
16810             opt->value = def;
16811             opt->min = min;
16812             opt->max = max;
16813             opt->type = Spin; // Slider;
16814         } else if((p = strstr(opt->name, " -string "))) {
16815             opt->textValue = p+9;
16816             opt->type = TextBox;
16817         } else if((p = strstr(opt->name, " -file "))) {
16818             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16819             opt->textValue = p+7;
16820             opt->type = FileName; // FileName;
16821         } else if((p = strstr(opt->name, " -path "))) {
16822             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16823             opt->textValue = p+7;
16824             opt->type = PathName; // PathName;
16825         } else if(p = strstr(opt->name, " -check ")) {
16826             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16827             opt->value = (def != 0);
16828             opt->type = CheckBox;
16829         } else if(p = strstr(opt->name, " -combo ")) {
16830             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16831             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16832             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16833             opt->value = n = 0;
16834             while(q = StrStr(q, " /// ")) {
16835                 n++; *q = 0;    // count choices, and null-terminate each of them
16836                 q += 5;
16837                 if(*q == '*') { // remember default, which is marked with * prefix
16838                     q++;
16839                     opt->value = n;
16840                 }
16841                 cps->comboList[cps->comboCnt++] = q;
16842             }
16843             cps->comboList[cps->comboCnt++] = NULL;
16844             opt->max = n + 1;
16845             opt->type = ComboBox;
16846         } else if(p = strstr(opt->name, " -button")) {
16847             opt->type = Button;
16848         } else if(p = strstr(opt->name, " -save")) {
16849             opt->type = SaveButton;
16850         } else return FALSE;
16851         *p = 0; // terminate option name
16852         // now look if the command-line options define a setting for this engine option.
16853         if(cps->optionSettings && cps->optionSettings[0])
16854             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16855         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16856           snprintf(buf, MSG_SIZ, "option %s", p);
16857                 if(p = strstr(buf, ",")) *p = 0;
16858                 if(q = strchr(buf, '=')) switch(opt->type) {
16859                     case ComboBox:
16860                         for(n=0; n<opt->max; n++)
16861                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16862                         break;
16863                     case TextBox:
16864                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16865                         break;
16866                     case Spin:
16867                     case CheckBox:
16868                         opt->value = atoi(q+1);
16869                     default:
16870                         break;
16871                 }
16872                 strcat(buf, "\n");
16873                 SendToProgram(buf, cps);
16874         }
16875         return TRUE;
16876 }
16877
16878 void
16879 FeatureDone (ChessProgramState *cps, int val)
16880 {
16881   DelayedEventCallback cb = GetDelayedEvent();
16882   if ((cb == InitBackEnd3 && cps == &first) ||
16883       (cb == SettingsMenuIfReady && cps == &second) ||
16884       (cb == LoadEngine) ||
16885       (cb == TwoMachinesEventIfReady)) {
16886     CancelDelayedEvent();
16887     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16888   }
16889   cps->initDone = val;
16890   if(val) cps->reload = FALSE;
16891 }
16892
16893 /* Parse feature command from engine */
16894 void
16895 ParseFeatures (char *args, ChessProgramState *cps)
16896 {
16897   char *p = args;
16898   char *q = NULL;
16899   int val;
16900   char buf[MSG_SIZ];
16901
16902   for (;;) {
16903     while (*p == ' ') p++;
16904     if (*p == NULLCHAR) return;
16905
16906     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16907     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16908     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16909     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16910     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16911     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16912     if (BoolFeature(&p, "reuse", &val, cps)) {
16913       /* Engine can disable reuse, but can't enable it if user said no */
16914       if (!val) cps->reuse = FALSE;
16915       continue;
16916     }
16917     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16918     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16919       if (gameMode == TwoMachinesPlay) {
16920         DisplayTwoMachinesTitle();
16921       } else {
16922         DisplayTitle("");
16923       }
16924       continue;
16925     }
16926     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16927     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16928     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16929     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16930     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16931     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16932     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16933     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16934     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16935     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16936     if (IntFeature(&p, "done", &val, cps)) {
16937       FeatureDone(cps, val);
16938       continue;
16939     }
16940     /* Added by Tord: */
16941     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16942     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16943     /* End of additions by Tord */
16944
16945     /* [HGM] added features: */
16946     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16947     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16948     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16949     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16950     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16951     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16952     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16953     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16954         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16955         FREE(cps->option[cps->nrOptions].name);
16956         cps->option[cps->nrOptions].name = q; q = NULL;
16957         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16958           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16959             SendToProgram(buf, cps);
16960             continue;
16961         }
16962         if(cps->nrOptions >= MAX_OPTIONS) {
16963             cps->nrOptions--;
16964             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16965             DisplayError(buf, 0);
16966         }
16967         continue;
16968     }
16969     /* End of additions by HGM */
16970
16971     /* unknown feature: complain and skip */
16972     q = p;
16973     while (*q && *q != '=') q++;
16974     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16975     SendToProgram(buf, cps);
16976     p = q;
16977     if (*p == '=') {
16978       p++;
16979       if (*p == '\"') {
16980         p++;
16981         while (*p && *p != '\"') p++;
16982         if (*p == '\"') p++;
16983       } else {
16984         while (*p && *p != ' ') p++;
16985       }
16986     }
16987   }
16988
16989 }
16990
16991 void
16992 PeriodicUpdatesEvent (int newState)
16993 {
16994     if (newState == appData.periodicUpdates)
16995       return;
16996
16997     appData.periodicUpdates=newState;
16998
16999     /* Display type changes, so update it now */
17000 //    DisplayAnalysis();
17001
17002     /* Get the ball rolling again... */
17003     if (newState) {
17004         AnalysisPeriodicEvent(1);
17005         StartAnalysisClock();
17006     }
17007 }
17008
17009 void
17010 PonderNextMoveEvent (int newState)
17011 {
17012     if (newState == appData.ponderNextMove) return;
17013     if (gameMode == EditPosition) EditPositionDone(TRUE);
17014     if (newState) {
17015         SendToProgram("hard\n", &first);
17016         if (gameMode == TwoMachinesPlay) {
17017             SendToProgram("hard\n", &second);
17018         }
17019     } else {
17020         SendToProgram("easy\n", &first);
17021         thinkOutput[0] = NULLCHAR;
17022         if (gameMode == TwoMachinesPlay) {
17023             SendToProgram("easy\n", &second);
17024         }
17025     }
17026     appData.ponderNextMove = newState;
17027 }
17028
17029 void
17030 NewSettingEvent (int option, int *feature, char *command, int value)
17031 {
17032     char buf[MSG_SIZ];
17033
17034     if (gameMode == EditPosition) EditPositionDone(TRUE);
17035     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17036     if(feature == NULL || *feature) SendToProgram(buf, &first);
17037     if (gameMode == TwoMachinesPlay) {
17038         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17039     }
17040 }
17041
17042 void
17043 ShowThinkingEvent ()
17044 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17045 {
17046     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17047     int newState = appData.showThinking
17048         // [HGM] thinking: other features now need thinking output as well
17049         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17050
17051     if (oldState == newState) return;
17052     oldState = newState;
17053     if (gameMode == EditPosition) EditPositionDone(TRUE);
17054     if (oldState) {
17055         SendToProgram("post\n", &first);
17056         if (gameMode == TwoMachinesPlay) {
17057             SendToProgram("post\n", &second);
17058         }
17059     } else {
17060         SendToProgram("nopost\n", &first);
17061         thinkOutput[0] = NULLCHAR;
17062         if (gameMode == TwoMachinesPlay) {
17063             SendToProgram("nopost\n", &second);
17064         }
17065     }
17066 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17067 }
17068
17069 void
17070 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17071 {
17072   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17073   if (pr == NoProc) return;
17074   AskQuestion(title, question, replyPrefix, pr);
17075 }
17076
17077 void
17078 TypeInEvent (char firstChar)
17079 {
17080     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17081         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17082         gameMode == AnalyzeMode || gameMode == EditGame ||
17083         gameMode == EditPosition || gameMode == IcsExamining ||
17084         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17085         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17086                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17087                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17088         gameMode == Training) PopUpMoveDialog(firstChar);
17089 }
17090
17091 void
17092 TypeInDoneEvent (char *move)
17093 {
17094         Board board;
17095         int n, fromX, fromY, toX, toY;
17096         char promoChar;
17097         ChessMove moveType;
17098
17099         // [HGM] FENedit
17100         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17101                 EditPositionPasteFEN(move);
17102                 return;
17103         }
17104         // [HGM] movenum: allow move number to be typed in any mode
17105         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17106           ToNrEvent(2*n-1);
17107           return;
17108         }
17109         // undocumented kludge: allow command-line option to be typed in!
17110         // (potentially fatal, and does not implement the effect of the option.)
17111         // should only be used for options that are values on which future decisions will be made,
17112         // and definitely not on options that would be used during initialization.
17113         if(strstr(move, "!!! -") == move) {
17114             ParseArgsFromString(move+4);
17115             return;
17116         }
17117
17118       if (gameMode != EditGame && currentMove != forwardMostMove &&
17119         gameMode != Training) {
17120         DisplayMoveError(_("Displayed move is not current"));
17121       } else {
17122         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17123           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17124         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17125         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17126           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17127           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17128         } else {
17129           DisplayMoveError(_("Could not parse move"));
17130         }
17131       }
17132 }
17133
17134 void
17135 DisplayMove (int moveNumber)
17136 {
17137     char message[MSG_SIZ];
17138     char res[MSG_SIZ];
17139     char cpThinkOutput[MSG_SIZ];
17140
17141     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17142
17143     if (moveNumber == forwardMostMove - 1 ||
17144         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17145
17146         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17147
17148         if (strchr(cpThinkOutput, '\n')) {
17149             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17150         }
17151     } else {
17152         *cpThinkOutput = NULLCHAR;
17153     }
17154
17155     /* [AS] Hide thinking from human user */
17156     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17157         *cpThinkOutput = NULLCHAR;
17158         if( thinkOutput[0] != NULLCHAR ) {
17159             int i;
17160
17161             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17162                 cpThinkOutput[i] = '.';
17163             }
17164             cpThinkOutput[i] = NULLCHAR;
17165             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17166         }
17167     }
17168
17169     if (moveNumber == forwardMostMove - 1 &&
17170         gameInfo.resultDetails != NULL) {
17171         if (gameInfo.resultDetails[0] == NULLCHAR) {
17172           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17173         } else {
17174           snprintf(res, MSG_SIZ, " {%s} %s",
17175                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17176         }
17177     } else {
17178         res[0] = NULLCHAR;
17179     }
17180
17181     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17182         DisplayMessage(res, cpThinkOutput);
17183     } else {
17184       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17185                 WhiteOnMove(moveNumber) ? " " : ".. ",
17186                 parseList[moveNumber], res);
17187         DisplayMessage(message, cpThinkOutput);
17188     }
17189 }
17190
17191 void
17192 DisplayComment (int moveNumber, char *text)
17193 {
17194     char title[MSG_SIZ];
17195
17196     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17197       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17198     } else {
17199       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17200               WhiteOnMove(moveNumber) ? " " : ".. ",
17201               parseList[moveNumber]);
17202     }
17203     if (text != NULL && (appData.autoDisplayComment || commentUp))
17204         CommentPopUp(title, text);
17205 }
17206
17207 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17208  * might be busy thinking or pondering.  It can be omitted if your
17209  * gnuchess is configured to stop thinking immediately on any user
17210  * input.  However, that gnuchess feature depends on the FIONREAD
17211  * ioctl, which does not work properly on some flavors of Unix.
17212  */
17213 void
17214 Attention (ChessProgramState *cps)
17215 {
17216 #if ATTENTION
17217     if (!cps->useSigint) return;
17218     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17219     switch (gameMode) {
17220       case MachinePlaysWhite:
17221       case MachinePlaysBlack:
17222       case TwoMachinesPlay:
17223       case IcsPlayingWhite:
17224       case IcsPlayingBlack:
17225       case AnalyzeMode:
17226       case AnalyzeFile:
17227         /* Skip if we know it isn't thinking */
17228         if (!cps->maybeThinking) return;
17229         if (appData.debugMode)
17230           fprintf(debugFP, "Interrupting %s\n", cps->which);
17231         InterruptChildProcess(cps->pr);
17232         cps->maybeThinking = FALSE;
17233         break;
17234       default:
17235         break;
17236     }
17237 #endif /*ATTENTION*/
17238 }
17239
17240 int
17241 CheckFlags ()
17242 {
17243     if (whiteTimeRemaining <= 0) {
17244         if (!whiteFlag) {
17245             whiteFlag = TRUE;
17246             if (appData.icsActive) {
17247                 if (appData.autoCallFlag &&
17248                     gameMode == IcsPlayingBlack && !blackFlag) {
17249                   SendToICS(ics_prefix);
17250                   SendToICS("flag\n");
17251                 }
17252             } else {
17253                 if (blackFlag) {
17254                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17255                 } else {
17256                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17257                     if (appData.autoCallFlag) {
17258                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17259                         return TRUE;
17260                     }
17261                 }
17262             }
17263         }
17264     }
17265     if (blackTimeRemaining <= 0) {
17266         if (!blackFlag) {
17267             blackFlag = TRUE;
17268             if (appData.icsActive) {
17269                 if (appData.autoCallFlag &&
17270                     gameMode == IcsPlayingWhite && !whiteFlag) {
17271                   SendToICS(ics_prefix);
17272                   SendToICS("flag\n");
17273                 }
17274             } else {
17275                 if (whiteFlag) {
17276                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17277                 } else {
17278                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17279                     if (appData.autoCallFlag) {
17280                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17281                         return TRUE;
17282                     }
17283                 }
17284             }
17285         }
17286     }
17287     return FALSE;
17288 }
17289
17290 void
17291 CheckTimeControl ()
17292 {
17293     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17294         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17295
17296     /*
17297      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17298      */
17299     if ( !WhiteOnMove(forwardMostMove) ) {
17300         /* White made time control */
17301         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17302         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17303         /* [HGM] time odds: correct new time quota for time odds! */
17304                                             / WhitePlayer()->timeOdds;
17305         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17306     } else {
17307         lastBlack -= blackTimeRemaining;
17308         /* Black made time control */
17309         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17310                                             / WhitePlayer()->other->timeOdds;
17311         lastWhite = whiteTimeRemaining;
17312     }
17313 }
17314
17315 void
17316 DisplayBothClocks ()
17317 {
17318     int wom = gameMode == EditPosition ?
17319       !blackPlaysFirst : WhiteOnMove(currentMove);
17320     DisplayWhiteClock(whiteTimeRemaining, wom);
17321     DisplayBlackClock(blackTimeRemaining, !wom);
17322 }
17323
17324
17325 /* Timekeeping seems to be a portability nightmare.  I think everyone
17326    has ftime(), but I'm really not sure, so I'm including some ifdefs
17327    to use other calls if you don't.  Clocks will be less accurate if
17328    you have neither ftime nor gettimeofday.
17329 */
17330
17331 /* VS 2008 requires the #include outside of the function */
17332 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17333 #include <sys/timeb.h>
17334 #endif
17335
17336 /* Get the current time as a TimeMark */
17337 void
17338 GetTimeMark (TimeMark *tm)
17339 {
17340 #if HAVE_GETTIMEOFDAY
17341
17342     struct timeval timeVal;
17343     struct timezone timeZone;
17344
17345     gettimeofday(&timeVal, &timeZone);
17346     tm->sec = (long) timeVal.tv_sec;
17347     tm->ms = (int) (timeVal.tv_usec / 1000L);
17348
17349 #else /*!HAVE_GETTIMEOFDAY*/
17350 #if HAVE_FTIME
17351
17352 // include <sys/timeb.h> / moved to just above start of function
17353     struct timeb timeB;
17354
17355     ftime(&timeB);
17356     tm->sec = (long) timeB.time;
17357     tm->ms = (int) timeB.millitm;
17358
17359 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17360     tm->sec = (long) time(NULL);
17361     tm->ms = 0;
17362 #endif
17363 #endif
17364 }
17365
17366 /* Return the difference in milliseconds between two
17367    time marks.  We assume the difference will fit in a long!
17368 */
17369 long
17370 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17371 {
17372     return 1000L*(tm2->sec - tm1->sec) +
17373            (long) (tm2->ms - tm1->ms);
17374 }
17375
17376
17377 /*
17378  * Code to manage the game clocks.
17379  *
17380  * In tournament play, black starts the clock and then white makes a move.
17381  * We give the human user a slight advantage if he is playing white---the
17382  * clocks don't run until he makes his first move, so it takes zero time.
17383  * Also, we don't account for network lag, so we could get out of sync
17384  * with GNU Chess's clock -- but then, referees are always right.
17385  */
17386
17387 static TimeMark tickStartTM;
17388 static long intendedTickLength;
17389
17390 long
17391 NextTickLength (long timeRemaining)
17392 {
17393     long nominalTickLength, nextTickLength;
17394
17395     if (timeRemaining > 0L && timeRemaining <= 10000L)
17396       nominalTickLength = 100L;
17397     else
17398       nominalTickLength = 1000L;
17399     nextTickLength = timeRemaining % nominalTickLength;
17400     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17401
17402     return nextTickLength;
17403 }
17404
17405 /* Adjust clock one minute up or down */
17406 void
17407 AdjustClock (Boolean which, int dir)
17408 {
17409     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17410     if(which) blackTimeRemaining += 60000*dir;
17411     else      whiteTimeRemaining += 60000*dir;
17412     DisplayBothClocks();
17413     adjustedClock = TRUE;
17414 }
17415
17416 /* Stop clocks and reset to a fresh time control */
17417 void
17418 ResetClocks ()
17419 {
17420     (void) StopClockTimer();
17421     if (appData.icsActive) {
17422         whiteTimeRemaining = blackTimeRemaining = 0;
17423     } else if (searchTime) {
17424         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17425         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17426     } else { /* [HGM] correct new time quote for time odds */
17427         whiteTC = blackTC = fullTimeControlString;
17428         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17429         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17430     }
17431     if (whiteFlag || blackFlag) {
17432         DisplayTitle("");
17433         whiteFlag = blackFlag = FALSE;
17434     }
17435     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17436     DisplayBothClocks();
17437     adjustedClock = FALSE;
17438 }
17439
17440 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17441
17442 /* Decrement running clock by amount of time that has passed */
17443 void
17444 DecrementClocks ()
17445 {
17446     long timeRemaining;
17447     long lastTickLength, fudge;
17448     TimeMark now;
17449
17450     if (!appData.clockMode) return;
17451     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17452
17453     GetTimeMark(&now);
17454
17455     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17456
17457     /* Fudge if we woke up a little too soon */
17458     fudge = intendedTickLength - lastTickLength;
17459     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17460
17461     if (WhiteOnMove(forwardMostMove)) {
17462         if(whiteNPS >= 0) lastTickLength = 0;
17463         timeRemaining = whiteTimeRemaining -= lastTickLength;
17464         if(timeRemaining < 0 && !appData.icsActive) {
17465             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17466             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17467                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17468                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17469             }
17470         }
17471         DisplayWhiteClock(whiteTimeRemaining - fudge,
17472                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17473     } else {
17474         if(blackNPS >= 0) lastTickLength = 0;
17475         timeRemaining = blackTimeRemaining -= lastTickLength;
17476         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17477             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17478             if(suddenDeath) {
17479                 blackStartMove = forwardMostMove;
17480                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17481             }
17482         }
17483         DisplayBlackClock(blackTimeRemaining - fudge,
17484                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17485     }
17486     if (CheckFlags()) return;
17487
17488     if(twoBoards) { // count down secondary board's clocks as well
17489         activePartnerTime -= lastTickLength;
17490         partnerUp = 1;
17491         if(activePartner == 'W')
17492             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17493         else
17494             DisplayBlackClock(activePartnerTime, TRUE);
17495         partnerUp = 0;
17496     }
17497
17498     tickStartTM = now;
17499     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17500     StartClockTimer(intendedTickLength);
17501
17502     /* if the time remaining has fallen below the alarm threshold, sound the
17503      * alarm. if the alarm has sounded and (due to a takeback or time control
17504      * with increment) the time remaining has increased to a level above the
17505      * threshold, reset the alarm so it can sound again.
17506      */
17507
17508     if (appData.icsActive && appData.icsAlarm) {
17509
17510         /* make sure we are dealing with the user's clock */
17511         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17512                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17513            )) return;
17514
17515         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17516             alarmSounded = FALSE;
17517         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17518             PlayAlarmSound();
17519             alarmSounded = TRUE;
17520         }
17521     }
17522 }
17523
17524
17525 /* A player has just moved, so stop the previously running
17526    clock and (if in clock mode) start the other one.
17527    We redisplay both clocks in case we're in ICS mode, because
17528    ICS gives us an update to both clocks after every move.
17529    Note that this routine is called *after* forwardMostMove
17530    is updated, so the last fractional tick must be subtracted
17531    from the color that is *not* on move now.
17532 */
17533 void
17534 SwitchClocks (int newMoveNr)
17535 {
17536     long lastTickLength;
17537     TimeMark now;
17538     int flagged = FALSE;
17539
17540     GetTimeMark(&now);
17541
17542     if (StopClockTimer() && appData.clockMode) {
17543         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17544         if (!WhiteOnMove(forwardMostMove)) {
17545             if(blackNPS >= 0) lastTickLength = 0;
17546             blackTimeRemaining -= lastTickLength;
17547            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17548 //         if(pvInfoList[forwardMostMove].time == -1)
17549                  pvInfoList[forwardMostMove].time =               // use GUI time
17550                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17551         } else {
17552            if(whiteNPS >= 0) lastTickLength = 0;
17553            whiteTimeRemaining -= lastTickLength;
17554            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17555 //         if(pvInfoList[forwardMostMove].time == -1)
17556                  pvInfoList[forwardMostMove].time =
17557                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17558         }
17559         flagged = CheckFlags();
17560     }
17561     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17562     CheckTimeControl();
17563
17564     if (flagged || !appData.clockMode) return;
17565
17566     switch (gameMode) {
17567       case MachinePlaysBlack:
17568       case MachinePlaysWhite:
17569       case BeginningOfGame:
17570         if (pausing) return;
17571         break;
17572
17573       case EditGame:
17574       case PlayFromGameFile:
17575       case IcsExamining:
17576         return;
17577
17578       default:
17579         break;
17580     }
17581
17582     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17583         if(WhiteOnMove(forwardMostMove))
17584              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17585         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17586     }
17587
17588     tickStartTM = now;
17589     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17590       whiteTimeRemaining : blackTimeRemaining);
17591     StartClockTimer(intendedTickLength);
17592 }
17593
17594
17595 /* Stop both clocks */
17596 void
17597 StopClocks ()
17598 {
17599     long lastTickLength;
17600     TimeMark now;
17601
17602     if (!StopClockTimer()) return;
17603     if (!appData.clockMode) return;
17604
17605     GetTimeMark(&now);
17606
17607     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17608     if (WhiteOnMove(forwardMostMove)) {
17609         if(whiteNPS >= 0) lastTickLength = 0;
17610         whiteTimeRemaining -= lastTickLength;
17611         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17612     } else {
17613         if(blackNPS >= 0) lastTickLength = 0;
17614         blackTimeRemaining -= lastTickLength;
17615         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17616     }
17617     CheckFlags();
17618 }
17619
17620 /* Start clock of player on move.  Time may have been reset, so
17621    if clock is already running, stop and restart it. */
17622 void
17623 StartClocks ()
17624 {
17625     (void) StopClockTimer(); /* in case it was running already */
17626     DisplayBothClocks();
17627     if (CheckFlags()) return;
17628
17629     if (!appData.clockMode) return;
17630     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17631
17632     GetTimeMark(&tickStartTM);
17633     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17634       whiteTimeRemaining : blackTimeRemaining);
17635
17636    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17637     whiteNPS = blackNPS = -1;
17638     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17639        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17640         whiteNPS = first.nps;
17641     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17642        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17643         blackNPS = first.nps;
17644     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17645         whiteNPS = second.nps;
17646     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17647         blackNPS = second.nps;
17648     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17649
17650     StartClockTimer(intendedTickLength);
17651 }
17652
17653 char *
17654 TimeString (long ms)
17655 {
17656     long second, minute, hour, day;
17657     char *sign = "";
17658     static char buf[32];
17659
17660     if (ms > 0 && ms <= 9900) {
17661       /* convert milliseconds to tenths, rounding up */
17662       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17663
17664       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17665       return buf;
17666     }
17667
17668     /* convert milliseconds to seconds, rounding up */
17669     /* use floating point to avoid strangeness of integer division
17670        with negative dividends on many machines */
17671     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17672
17673     if (second < 0) {
17674         sign = "-";
17675         second = -second;
17676     }
17677
17678     day = second / (60 * 60 * 24);
17679     second = second % (60 * 60 * 24);
17680     hour = second / (60 * 60);
17681     second = second % (60 * 60);
17682     minute = second / 60;
17683     second = second % 60;
17684
17685     if (day > 0)
17686       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17687               sign, day, hour, minute, second);
17688     else if (hour > 0)
17689       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17690     else
17691       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17692
17693     return buf;
17694 }
17695
17696
17697 /*
17698  * This is necessary because some C libraries aren't ANSI C compliant yet.
17699  */
17700 char *
17701 StrStr (char *string, char *match)
17702 {
17703     int i, length;
17704
17705     length = strlen(match);
17706
17707     for (i = strlen(string) - length; i >= 0; i--, string++)
17708       if (!strncmp(match, string, length))
17709         return string;
17710
17711     return NULL;
17712 }
17713
17714 char *
17715 StrCaseStr (char *string, char *match)
17716 {
17717     int i, j, length;
17718
17719     length = strlen(match);
17720
17721     for (i = strlen(string) - length; i >= 0; i--, string++) {
17722         for (j = 0; j < length; j++) {
17723             if (ToLower(match[j]) != ToLower(string[j]))
17724               break;
17725         }
17726         if (j == length) return string;
17727     }
17728
17729     return NULL;
17730 }
17731
17732 #ifndef _amigados
17733 int
17734 StrCaseCmp (char *s1, char *s2)
17735 {
17736     char c1, c2;
17737
17738     for (;;) {
17739         c1 = ToLower(*s1++);
17740         c2 = ToLower(*s2++);
17741         if (c1 > c2) return 1;
17742         if (c1 < c2) return -1;
17743         if (c1 == NULLCHAR) return 0;
17744     }
17745 }
17746
17747
17748 int
17749 ToLower (int c)
17750 {
17751     return isupper(c) ? tolower(c) : c;
17752 }
17753
17754
17755 int
17756 ToUpper (int c)
17757 {
17758     return islower(c) ? toupper(c) : c;
17759 }
17760 #endif /* !_amigados    */
17761
17762 char *
17763 StrSave (char *s)
17764 {
17765   char *ret;
17766
17767   if ((ret = (char *) malloc(strlen(s) + 1)))
17768     {
17769       safeStrCpy(ret, s, strlen(s)+1);
17770     }
17771   return ret;
17772 }
17773
17774 char *
17775 StrSavePtr (char *s, char **savePtr)
17776 {
17777     if (*savePtr) {
17778         free(*savePtr);
17779     }
17780     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17781       safeStrCpy(*savePtr, s, strlen(s)+1);
17782     }
17783     return(*savePtr);
17784 }
17785
17786 char *
17787 PGNDate ()
17788 {
17789     time_t clock;
17790     struct tm *tm;
17791     char buf[MSG_SIZ];
17792
17793     clock = time((time_t *)NULL);
17794     tm = localtime(&clock);
17795     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17796             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17797     return StrSave(buf);
17798 }
17799
17800
17801 char *
17802 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17803 {
17804     int i, j, fromX, fromY, toX, toY;
17805     int whiteToPlay;
17806     char buf[MSG_SIZ];
17807     char *p, *q;
17808     int emptycount;
17809     ChessSquare piece;
17810
17811     whiteToPlay = (gameMode == EditPosition) ?
17812       !blackPlaysFirst : (move % 2 == 0);
17813     p = buf;
17814
17815     /* Piece placement data */
17816     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17817         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17818         emptycount = 0;
17819         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17820             if (boards[move][i][j] == EmptySquare) {
17821                 emptycount++;
17822             } else { ChessSquare piece = boards[move][i][j];
17823                 if (emptycount > 0) {
17824                     if(emptycount<10) /* [HGM] can be >= 10 */
17825                         *p++ = '0' + emptycount;
17826                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17827                     emptycount = 0;
17828                 }
17829                 if(PieceToChar(piece) == '+') {
17830                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17831                     *p++ = '+';
17832                     piece = (ChessSquare)(CHUDEMOTED piece);
17833                 }
17834                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17835                 if(p[-1] == '~') {
17836                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17837                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17838                     *p++ = '~';
17839                 }
17840             }
17841         }
17842         if (emptycount > 0) {
17843             if(emptycount<10) /* [HGM] can be >= 10 */
17844                 *p++ = '0' + emptycount;
17845             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17846             emptycount = 0;
17847         }
17848         *p++ = '/';
17849     }
17850     *(p - 1) = ' ';
17851
17852     /* [HGM] print Crazyhouse or Shogi holdings */
17853     if( gameInfo.holdingsWidth ) {
17854         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17855         q = p;
17856         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17857             piece = boards[move][i][BOARD_WIDTH-1];
17858             if( piece != EmptySquare )
17859               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17860                   *p++ = PieceToChar(piece);
17861         }
17862         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17863             piece = boards[move][BOARD_HEIGHT-i-1][0];
17864             if( piece != EmptySquare )
17865               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17866                   *p++ = PieceToChar(piece);
17867         }
17868
17869         if( q == p ) *p++ = '-';
17870         *p++ = ']';
17871         *p++ = ' ';
17872     }
17873
17874     /* Active color */
17875     *p++ = whiteToPlay ? 'w' : 'b';
17876     *p++ = ' ';
17877
17878   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17879     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17880   } else {
17881   if(nrCastlingRights) {
17882      int handW=0, handB=0;
17883      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17884         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17885         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17886      }
17887      q = p;
17888      if(appData.fischerCastling) {
17889         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17890            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17891                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17892         } else {
17893        /* [HGM] write directly from rights */
17894            if(boards[move][CASTLING][2] != NoRights &&
17895               boards[move][CASTLING][0] != NoRights   )
17896                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17897            if(boards[move][CASTLING][2] != NoRights &&
17898               boards[move][CASTLING][1] != NoRights   )
17899                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17900         }
17901         if(handB) {
17902            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17903                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17904         } else {
17905            if(boards[move][CASTLING][5] != NoRights &&
17906               boards[move][CASTLING][3] != NoRights   )
17907                 *p++ = boards[move][CASTLING][3] + AAA;
17908            if(boards[move][CASTLING][5] != NoRights &&
17909               boards[move][CASTLING][4] != NoRights   )
17910                 *p++ = boards[move][CASTLING][4] + AAA;
17911         }
17912      } else {
17913
17914         /* [HGM] write true castling rights */
17915         if( nrCastlingRights == 6 ) {
17916             int q, k=0;
17917             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17918                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17919             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17920                  boards[move][CASTLING][2] != NoRights  );
17921             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17922                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17923                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17924                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17925             }
17926             if(q) *p++ = 'Q';
17927             k = 0;
17928             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17929                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17930             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17931                  boards[move][CASTLING][5] != NoRights  );
17932             if(handB) {
17933                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17934                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17935                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17936             }
17937             if(q) *p++ = 'q';
17938         }
17939      }
17940      if (q == p) *p++ = '-'; /* No castling rights */
17941      *p++ = ' ';
17942   }
17943
17944   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17945      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17946      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17947     /* En passant target square */
17948     if (move > backwardMostMove) {
17949         fromX = moveList[move - 1][0] - AAA;
17950         fromY = moveList[move - 1][1] - ONE;
17951         toX = moveList[move - 1][2] - AAA;
17952         toY = moveList[move - 1][3] - ONE;
17953         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17954             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17955             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17956             fromX == toX) {
17957             /* 2-square pawn move just happened */
17958             *p++ = toX + AAA;
17959             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17960         } else {
17961             *p++ = '-';
17962         }
17963     } else if(move == backwardMostMove) {
17964         // [HGM] perhaps we should always do it like this, and forget the above?
17965         if((signed char)boards[move][EP_STATUS] >= 0) {
17966             *p++ = boards[move][EP_STATUS] + AAA;
17967             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17968         } else {
17969             *p++ = '-';
17970         }
17971     } else {
17972         *p++ = '-';
17973     }
17974     *p++ = ' ';
17975   }
17976   }
17977
17978     if(moveCounts)
17979     {   int i = 0, j=move;
17980
17981         /* [HGM] find reversible plies */
17982         if (appData.debugMode) { int k;
17983             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17984             for(k=backwardMostMove; k<=forwardMostMove; k++)
17985                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17986
17987         }
17988
17989         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17990         if( j == backwardMostMove ) i += initialRulePlies;
17991         sprintf(p, "%d ", i);
17992         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17993
17994         /* Fullmove number */
17995         sprintf(p, "%d", (move / 2) + 1);
17996     } else *--p = NULLCHAR;
17997
17998     return StrSave(buf);
17999 }
18000
18001 Boolean
18002 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18003 {
18004     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18005     char *p, c;
18006     int emptycount, virgin[BOARD_FILES];
18007     ChessSquare piece;
18008
18009     p = fen;
18010
18011     /* Piece placement data */
18012     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18013         j = 0;
18014         for (;;) {
18015             if (*p == '/' || *p == ' ' || *p == '[' ) {
18016                 if(j > w) w = j;
18017                 emptycount = gameInfo.boardWidth - j;
18018                 while (emptycount--)
18019                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18020                 if (*p == '/') p++;
18021                 else if(autoSize) { // we stumbled unexpectedly into end of board
18022                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18023                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18024                     }
18025                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18026                 }
18027                 break;
18028 #if(BOARD_FILES >= 10)*0
18029             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18030                 p++; emptycount=10;
18031                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18032                 while (emptycount--)
18033                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18034 #endif
18035             } else if (*p == '*') {
18036                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18037             } else if (isdigit(*p)) {
18038                 emptycount = *p++ - '0';
18039                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18040                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18041                 while (emptycount--)
18042                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18043             } else if (*p == '<') {
18044                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18045                 else if (i != 0 || !shuffle) return FALSE;
18046                 p++;
18047             } else if (shuffle && *p == '>') {
18048                 p++; // for now ignore closing shuffle range, and assume rank-end
18049             } else if (*p == '?') {
18050                 if (j >= gameInfo.boardWidth) return FALSE;
18051                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18052                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18053             } else if (*p == '+' || isalpha(*p)) {
18054                 if (j >= gameInfo.boardWidth) return FALSE;
18055                 if(*p=='+') {
18056                     piece = CharToPiece(*++p);
18057                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18058                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18059                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18060                 } else piece = CharToPiece(*p++);
18061
18062                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18063                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18064                     piece = (ChessSquare) (PROMOTED piece);
18065                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18066                     p++;
18067                 }
18068                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18069                 if(piece == WhiteKing) wKingRank = i;
18070                 if(piece == BlackKing) bKingRank = i;
18071             } else {
18072                 return FALSE;
18073             }
18074         }
18075     }
18076     while (*p == '/' || *p == ' ') p++;
18077
18078     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18079
18080     /* [HGM] by default clear Crazyhouse holdings, if present */
18081     if(gameInfo.holdingsWidth) {
18082        for(i=0; i<BOARD_HEIGHT; i++) {
18083            board[i][0]             = EmptySquare; /* black holdings */
18084            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18085            board[i][1]             = (ChessSquare) 0; /* black counts */
18086            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18087        }
18088     }
18089
18090     /* [HGM] look for Crazyhouse holdings here */
18091     while(*p==' ') p++;
18092     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18093         int swap=0, wcnt=0, bcnt=0;
18094         if(*p == '[') p++;
18095         if(*p == '<') swap++, p++;
18096         if(*p == '-' ) p++; /* empty holdings */ else {
18097             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18098             /* if we would allow FEN reading to set board size, we would   */
18099             /* have to add holdings and shift the board read so far here   */
18100             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18101                 p++;
18102                 if((int) piece >= (int) BlackPawn ) {
18103                     i = (int)piece - (int)BlackPawn;
18104                     i = PieceToNumber((ChessSquare)i);
18105                     if( i >= gameInfo.holdingsSize ) return FALSE;
18106                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18107                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18108                     bcnt++;
18109                 } else {
18110                     i = (int)piece - (int)WhitePawn;
18111                     i = PieceToNumber((ChessSquare)i);
18112                     if( i >= gameInfo.holdingsSize ) return FALSE;
18113                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18114                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18115                     wcnt++;
18116                 }
18117             }
18118             if(subst) { // substitute back-rank question marks by holdings pieces
18119                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18120                     int k, m, n = bcnt + 1;
18121                     if(board[0][j] == ClearBoard) {
18122                         if(!wcnt) return FALSE;
18123                         n = rand() % wcnt;
18124                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18125                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18126                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18127                             break;
18128                         }
18129                     }
18130                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18131                         if(!bcnt) return FALSE;
18132                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18133                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18134                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18135                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18136                             break;
18137                         }
18138                     }
18139                 }
18140                 subst = 0;
18141             }
18142         }
18143         if(*p == ']') p++;
18144     }
18145
18146     if(subst) return FALSE; // substitution requested, but no holdings
18147
18148     while(*p == ' ') p++;
18149
18150     /* Active color */
18151     c = *p++;
18152     if(appData.colorNickNames) {
18153       if( c == appData.colorNickNames[0] ) c = 'w'; else
18154       if( c == appData.colorNickNames[1] ) c = 'b';
18155     }
18156     switch (c) {
18157       case 'w':
18158         *blackPlaysFirst = FALSE;
18159         break;
18160       case 'b':
18161         *blackPlaysFirst = TRUE;
18162         break;
18163       default:
18164         return FALSE;
18165     }
18166
18167     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18168     /* return the extra info in global variiables             */
18169
18170     while(*p==' ') p++;
18171
18172     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18173         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18174         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18175     }
18176
18177     /* set defaults in case FEN is incomplete */
18178     board[EP_STATUS] = EP_UNKNOWN;
18179     for(i=0; i<nrCastlingRights; i++ ) {
18180         board[CASTLING][i] =
18181             appData.fischerCastling ? NoRights : initialRights[i];
18182     }   /* assume possible unless obviously impossible */
18183     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18184     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18185     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18186                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18187     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18188     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18189     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18190                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18191     FENrulePlies = 0;
18192
18193     if(nrCastlingRights) {
18194       int fischer = 0;
18195       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18196       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18197           /* castling indicator present, so default becomes no castlings */
18198           for(i=0; i<nrCastlingRights; i++ ) {
18199                  board[CASTLING][i] = NoRights;
18200           }
18201       }
18202       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18203              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18204              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18205              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18206         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18207
18208         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18209             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18210             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18211         }
18212         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18213             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18214         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18215                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18216         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18217                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18218         switch(c) {
18219           case'K':
18220               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18221               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18222               board[CASTLING][2] = whiteKingFile;
18223               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18224               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18225               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18226               break;
18227           case'Q':
18228               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18229               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18230               board[CASTLING][2] = whiteKingFile;
18231               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18232               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18233               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18234               break;
18235           case'k':
18236               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18237               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18238               board[CASTLING][5] = blackKingFile;
18239               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18240               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18241               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18242               break;
18243           case'q':
18244               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18245               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18246               board[CASTLING][5] = blackKingFile;
18247               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18248               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18249               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18250           case '-':
18251               break;
18252           default: /* FRC castlings */
18253               if(c >= 'a') { /* black rights */
18254                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18255                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18256                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18257                   if(i == BOARD_RGHT) break;
18258                   board[CASTLING][5] = i;
18259                   c -= AAA;
18260                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18261                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18262                   if(c > i)
18263                       board[CASTLING][3] = c;
18264                   else
18265                       board[CASTLING][4] = c;
18266               } else { /* white rights */
18267                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18268                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18269                     if(board[0][i] == WhiteKing) break;
18270                   if(i == BOARD_RGHT) break;
18271                   board[CASTLING][2] = i;
18272                   c -= AAA - 'a' + 'A';
18273                   if(board[0][c] >= WhiteKing) break;
18274                   if(c > i)
18275                       board[CASTLING][0] = c;
18276                   else
18277                       board[CASTLING][1] = c;
18278               }
18279         }
18280       }
18281       for(i=0; i<nrCastlingRights; i++)
18282         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18283       if(gameInfo.variant == VariantSChess)
18284         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18285       if(fischer && shuffle) appData.fischerCastling = TRUE;
18286     if (appData.debugMode) {
18287         fprintf(debugFP, "FEN castling rights:");
18288         for(i=0; i<nrCastlingRights; i++)
18289         fprintf(debugFP, " %d", board[CASTLING][i]);
18290         fprintf(debugFP, "\n");
18291     }
18292
18293       while(*p==' ') p++;
18294     }
18295
18296     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18297
18298     /* read e.p. field in games that know e.p. capture */
18299     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18300        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18301        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18302       if(*p=='-') {
18303         p++; board[EP_STATUS] = EP_NONE;
18304       } else {
18305          char c = *p++ - AAA;
18306
18307          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18308          if(*p >= '0' && *p <='9') p++;
18309          board[EP_STATUS] = c;
18310       }
18311     }
18312
18313
18314     if(sscanf(p, "%d", &i) == 1) {
18315         FENrulePlies = i; /* 50-move ply counter */
18316         /* (The move number is still ignored)    */
18317     }
18318
18319     return TRUE;
18320 }
18321
18322 void
18323 EditPositionPasteFEN (char *fen)
18324 {
18325   if (fen != NULL) {
18326     Board initial_position;
18327
18328     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18329       DisplayError(_("Bad FEN position in clipboard"), 0);
18330       return ;
18331     } else {
18332       int savedBlackPlaysFirst = blackPlaysFirst;
18333       EditPositionEvent();
18334       blackPlaysFirst = savedBlackPlaysFirst;
18335       CopyBoard(boards[0], initial_position);
18336       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18337       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18338       DisplayBothClocks();
18339       DrawPosition(FALSE, boards[currentMove]);
18340     }
18341   }
18342 }
18343
18344 static char cseq[12] = "\\   ";
18345
18346 Boolean
18347 set_cont_sequence (char *new_seq)
18348 {
18349     int len;
18350     Boolean ret;
18351
18352     // handle bad attempts to set the sequence
18353         if (!new_seq)
18354                 return 0; // acceptable error - no debug
18355
18356     len = strlen(new_seq);
18357     ret = (len > 0) && (len < sizeof(cseq));
18358     if (ret)
18359       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18360     else if (appData.debugMode)
18361       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18362     return ret;
18363 }
18364
18365 /*
18366     reformat a source message so words don't cross the width boundary.  internal
18367     newlines are not removed.  returns the wrapped size (no null character unless
18368     included in source message).  If dest is NULL, only calculate the size required
18369     for the dest buffer.  lp argument indicats line position upon entry, and it's
18370     passed back upon exit.
18371 */
18372 int
18373 wrap (char *dest, char *src, int count, int width, int *lp)
18374 {
18375     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18376
18377     cseq_len = strlen(cseq);
18378     old_line = line = *lp;
18379     ansi = len = clen = 0;
18380
18381     for (i=0; i < count; i++)
18382     {
18383         if (src[i] == '\033')
18384             ansi = 1;
18385
18386         // if we hit the width, back up
18387         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18388         {
18389             // store i & len in case the word is too long
18390             old_i = i, old_len = len;
18391
18392             // find the end of the last word
18393             while (i && src[i] != ' ' && src[i] != '\n')
18394             {
18395                 i--;
18396                 len--;
18397             }
18398
18399             // word too long?  restore i & len before splitting it
18400             if ((old_i-i+clen) >= width)
18401             {
18402                 i = old_i;
18403                 len = old_len;
18404             }
18405
18406             // extra space?
18407             if (i && src[i-1] == ' ')
18408                 len--;
18409
18410             if (src[i] != ' ' && src[i] != '\n')
18411             {
18412                 i--;
18413                 if (len)
18414                     len--;
18415             }
18416
18417             // now append the newline and continuation sequence
18418             if (dest)
18419                 dest[len] = '\n';
18420             len++;
18421             if (dest)
18422                 strncpy(dest+len, cseq, cseq_len);
18423             len += cseq_len;
18424             line = cseq_len;
18425             clen = cseq_len;
18426             continue;
18427         }
18428
18429         if (dest)
18430             dest[len] = src[i];
18431         len++;
18432         if (!ansi)
18433             line++;
18434         if (src[i] == '\n')
18435             line = 0;
18436         if (src[i] == 'm')
18437             ansi = 0;
18438     }
18439     if (dest && appData.debugMode)
18440     {
18441         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18442             count, width, line, len, *lp);
18443         show_bytes(debugFP, src, count);
18444         fprintf(debugFP, "\ndest: ");
18445         show_bytes(debugFP, dest, len);
18446         fprintf(debugFP, "\n");
18447     }
18448     *lp = dest ? line : old_line;
18449
18450     return len;
18451 }
18452
18453 // [HGM] vari: routines for shelving variations
18454 Boolean modeRestore = FALSE;
18455
18456 void
18457 PushInner (int firstMove, int lastMove)
18458 {
18459         int i, j, nrMoves = lastMove - firstMove;
18460
18461         // push current tail of game on stack
18462         savedResult[storedGames] = gameInfo.result;
18463         savedDetails[storedGames] = gameInfo.resultDetails;
18464         gameInfo.resultDetails = NULL;
18465         savedFirst[storedGames] = firstMove;
18466         savedLast [storedGames] = lastMove;
18467         savedFramePtr[storedGames] = framePtr;
18468         framePtr -= nrMoves; // reserve space for the boards
18469         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18470             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18471             for(j=0; j<MOVE_LEN; j++)
18472                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18473             for(j=0; j<2*MOVE_LEN; j++)
18474                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18475             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18476             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18477             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18478             pvInfoList[firstMove+i-1].depth = 0;
18479             commentList[framePtr+i] = commentList[firstMove+i];
18480             commentList[firstMove+i] = NULL;
18481         }
18482
18483         storedGames++;
18484         forwardMostMove = firstMove; // truncate game so we can start variation
18485 }
18486
18487 void
18488 PushTail (int firstMove, int lastMove)
18489 {
18490         if(appData.icsActive) { // only in local mode
18491                 forwardMostMove = currentMove; // mimic old ICS behavior
18492                 return;
18493         }
18494         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18495
18496         PushInner(firstMove, lastMove);
18497         if(storedGames == 1) GreyRevert(FALSE);
18498         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18499 }
18500
18501 void
18502 PopInner (Boolean annotate)
18503 {
18504         int i, j, nrMoves;
18505         char buf[8000], moveBuf[20];
18506
18507         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18508         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18509         nrMoves = savedLast[storedGames] - currentMove;
18510         if(annotate) {
18511                 int cnt = 10;
18512                 if(!WhiteOnMove(currentMove))
18513                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18514                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18515                 for(i=currentMove; i<forwardMostMove; i++) {
18516                         if(WhiteOnMove(i))
18517                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18518                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18519                         strcat(buf, moveBuf);
18520                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18521                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18522                 }
18523                 strcat(buf, ")");
18524         }
18525         for(i=1; i<=nrMoves; i++) { // copy last variation back
18526             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18527             for(j=0; j<MOVE_LEN; j++)
18528                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18529             for(j=0; j<2*MOVE_LEN; j++)
18530                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18531             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18532             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18533             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18534             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18535             commentList[currentMove+i] = commentList[framePtr+i];
18536             commentList[framePtr+i] = NULL;
18537         }
18538         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18539         framePtr = savedFramePtr[storedGames];
18540         gameInfo.result = savedResult[storedGames];
18541         if(gameInfo.resultDetails != NULL) {
18542             free(gameInfo.resultDetails);
18543       }
18544         gameInfo.resultDetails = savedDetails[storedGames];
18545         forwardMostMove = currentMove + nrMoves;
18546 }
18547
18548 Boolean
18549 PopTail (Boolean annotate)
18550 {
18551         if(appData.icsActive) return FALSE; // only in local mode
18552         if(!storedGames) return FALSE; // sanity
18553         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18554
18555         PopInner(annotate);
18556         if(currentMove < forwardMostMove) ForwardEvent(); else
18557         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18558
18559         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18560         return TRUE;
18561 }
18562
18563 void
18564 CleanupTail ()
18565 {       // remove all shelved variations
18566         int i;
18567         for(i=0; i<storedGames; i++) {
18568             if(savedDetails[i])
18569                 free(savedDetails[i]);
18570             savedDetails[i] = NULL;
18571         }
18572         for(i=framePtr; i<MAX_MOVES; i++) {
18573                 if(commentList[i]) free(commentList[i]);
18574                 commentList[i] = NULL;
18575         }
18576         framePtr = MAX_MOVES-1;
18577         storedGames = 0;
18578 }
18579
18580 void
18581 LoadVariation (int index, char *text)
18582 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18583         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18584         int level = 0, move;
18585
18586         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18587         // first find outermost bracketing variation
18588         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18589             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18590                 if(*p == '{') wait = '}'; else
18591                 if(*p == '[') wait = ']'; else
18592                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18593                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18594             }
18595             if(*p == wait) wait = NULLCHAR; // closing ]} found
18596             p++;
18597         }
18598         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18599         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18600         end[1] = NULLCHAR; // clip off comment beyond variation
18601         ToNrEvent(currentMove-1);
18602         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18603         // kludge: use ParsePV() to append variation to game
18604         move = currentMove;
18605         ParsePV(start, TRUE, TRUE);
18606         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18607         ClearPremoveHighlights();
18608         CommentPopDown();
18609         ToNrEvent(currentMove+1);
18610 }
18611
18612 void
18613 LoadTheme ()
18614 {
18615     char *p, *q, buf[MSG_SIZ];
18616     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18617         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18618         ParseArgsFromString(buf);
18619         ActivateTheme(TRUE); // also redo colors
18620         return;
18621     }
18622     p = nickName;
18623     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18624     {
18625         int len;
18626         q = appData.themeNames;
18627         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18628       if(appData.useBitmaps) {
18629         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18630                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18631                 appData.liteBackTextureMode,
18632                 appData.darkBackTextureMode );
18633       } else {
18634         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18635                 Col2Text(2),   // lightSquareColor
18636                 Col2Text(3) ); // darkSquareColor
18637       }
18638       if(appData.useBorder) {
18639         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18640                 appData.border);
18641       } else {
18642         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18643       }
18644       if(appData.useFont) {
18645         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18646                 appData.renderPiecesWithFont,
18647                 appData.fontToPieceTable,
18648                 Col2Text(9),    // appData.fontBackColorWhite
18649                 Col2Text(10) ); // appData.fontForeColorBlack
18650       } else {
18651         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18652                 appData.pieceDirectory);
18653         if(!appData.pieceDirectory[0])
18654           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18655                 Col2Text(0),   // whitePieceColor
18656                 Col2Text(1) ); // blackPieceColor
18657       }
18658       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18659                 Col2Text(4),   // highlightSquareColor
18660                 Col2Text(5) ); // premoveHighlightColor
18661         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18662         if(insert != q) insert[-1] = NULLCHAR;
18663         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18664         if(q)   free(q);
18665     }
18666     ActivateTheme(FALSE);
18667 }