Fix reading of pieceToChar string and piece command
[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] == '@') { // illegal drop
5531           *fromX = WhiteOnMove(moveNum) ?
5532             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5533             (int) CharToPiece(ToLower(currentMoveString[0]));
5534           goto drop;
5535         }
5536         *fromX = currentMoveString[0] - AAA;
5537         *fromY = currentMoveString[1] - ONE;
5538         *toX = currentMoveString[2] - AAA;
5539         *toY = currentMoveString[3] - ONE;
5540         *promoChar = currentMoveString[4];
5541         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5542             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5543     if (appData.debugMode) {
5544         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5545     }
5546             *fromX = *fromY = *toX = *toY = 0;
5547             return FALSE;
5548         }
5549         if (appData.testLegality) {
5550           return (*moveType != IllegalMove);
5551         } else {
5552           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5553                          // [HGM] lion: if this is a double move we are less critical
5554                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5555         }
5556
5557       case WhiteDrop:
5558       case BlackDrop:
5559         *fromX = *moveType == WhiteDrop ?
5560           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5561           (int) CharToPiece(ToLower(currentMoveString[0]));
5562       drop:
5563         *fromY = DROP_RANK;
5564         *toX = currentMoveString[2] - AAA;
5565         *toY = currentMoveString[3] - ONE;
5566         *promoChar = NULLCHAR;
5567         return TRUE;
5568
5569       case AmbiguousMove:
5570       case ImpossibleMove:
5571       case EndOfFile:
5572       case ElapsedTime:
5573       case Comment:
5574       case PGNTag:
5575       case NAG:
5576       case WhiteWins:
5577       case BlackWins:
5578       case GameIsDrawn:
5579       default:
5580     if (appData.debugMode) {
5581         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5582     }
5583         /* bug? */
5584         *fromX = *fromY = *toX = *toY = 0;
5585         *promoChar = NULLCHAR;
5586         return FALSE;
5587     }
5588 }
5589
5590 Boolean pushed = FALSE;
5591 char *lastParseAttempt;
5592
5593 void
5594 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5595 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5596   int fromX, fromY, toX, toY; char promoChar;
5597   ChessMove moveType;
5598   Boolean valid;
5599   int nr = 0;
5600
5601   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5602   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5603     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5604     pushed = TRUE;
5605   }
5606   endPV = forwardMostMove;
5607   do {
5608     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5609     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5610     lastParseAttempt = pv;
5611     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5612     if(!valid && nr == 0 &&
5613        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5614         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5615         // Hande case where played move is different from leading PV move
5616         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5617         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5618         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5619         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5620           endPV += 2; // if position different, keep this
5621           moveList[endPV-1][0] = fromX + AAA;
5622           moveList[endPV-1][1] = fromY + ONE;
5623           moveList[endPV-1][2] = toX + AAA;
5624           moveList[endPV-1][3] = toY + ONE;
5625           parseList[endPV-1][0] = NULLCHAR;
5626           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5627         }
5628       }
5629     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5630     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5631     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5632     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5633         valid++; // allow comments in PV
5634         continue;
5635     }
5636     nr++;
5637     if(endPV+1 > framePtr) break; // no space, truncate
5638     if(!valid) break;
5639     endPV++;
5640     CopyBoard(boards[endPV], boards[endPV-1]);
5641     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5642     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5643     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5644     CoordsToAlgebraic(boards[endPV - 1],
5645                              PosFlags(endPV - 1),
5646                              fromY, fromX, toY, toX, promoChar,
5647                              parseList[endPV - 1]);
5648   } while(valid);
5649   if(atEnd == 2) return; // used hidden, for PV conversion
5650   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5651   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5652   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5653                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5654   DrawPosition(TRUE, boards[currentMove]);
5655 }
5656
5657 int
5658 MultiPV (ChessProgramState *cps)
5659 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5660         int i;
5661         for(i=0; i<cps->nrOptions; i++)
5662             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5663                 return i;
5664         return -1;
5665 }
5666
5667 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5668
5669 Boolean
5670 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5671 {
5672         int startPV, multi, lineStart, origIndex = index;
5673         char *p, buf2[MSG_SIZ];
5674         ChessProgramState *cps = (pane ? &second : &first);
5675
5676         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5677         lastX = x; lastY = y;
5678         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5679         lineStart = startPV = index;
5680         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5681         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5682         index = startPV;
5683         do{ while(buf[index] && buf[index] != '\n') index++;
5684         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5685         buf[index] = 0;
5686         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5687                 int n = cps->option[multi].value;
5688                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5689                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5690                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5691                 cps->option[multi].value = n;
5692                 *start = *end = 0;
5693                 return FALSE;
5694         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5695                 ExcludeClick(origIndex - lineStart);
5696                 return FALSE;
5697         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5698                 Collapse(origIndex - lineStart);
5699                 return FALSE;
5700         }
5701         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5702         *start = startPV; *end = index-1;
5703         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5704         return TRUE;
5705 }
5706
5707 char *
5708 PvToSAN (char *pv)
5709 {
5710         static char buf[10*MSG_SIZ];
5711         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5712         *buf = NULLCHAR;
5713         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5714         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5715         for(i = forwardMostMove; i<endPV; i++){
5716             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5717             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5718             k += strlen(buf+k);
5719         }
5720         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5721         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5722         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5723         endPV = savedEnd;
5724         return buf;
5725 }
5726
5727 Boolean
5728 LoadPV (int x, int y)
5729 { // called on right mouse click to load PV
5730   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5731   lastX = x; lastY = y;
5732   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5733   extendGame = FALSE;
5734   return TRUE;
5735 }
5736
5737 void
5738 UnLoadPV ()
5739 {
5740   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5741   if(endPV < 0) return;
5742   if(appData.autoCopyPV) CopyFENToClipboard();
5743   endPV = -1;
5744   if(extendGame && currentMove > forwardMostMove) {
5745         Boolean saveAnimate = appData.animate;
5746         if(pushed) {
5747             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5748                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5749             } else storedGames--; // abandon shelved tail of original game
5750         }
5751         pushed = FALSE;
5752         forwardMostMove = currentMove;
5753         currentMove = oldFMM;
5754         appData.animate = FALSE;
5755         ToNrEvent(forwardMostMove);
5756         appData.animate = saveAnimate;
5757   }
5758   currentMove = forwardMostMove;
5759   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5760   ClearPremoveHighlights();
5761   DrawPosition(TRUE, boards[currentMove]);
5762 }
5763
5764 void
5765 MovePV (int x, int y, int h)
5766 { // step through PV based on mouse coordinates (called on mouse move)
5767   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5768
5769   // we must somehow check if right button is still down (might be released off board!)
5770   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5771   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5772   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5773   if(!step) return;
5774   lastX = x; lastY = y;
5775
5776   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5777   if(endPV < 0) return;
5778   if(y < margin) step = 1; else
5779   if(y > h - margin) step = -1;
5780   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5781   currentMove += step;
5782   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5783   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5784                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5785   DrawPosition(FALSE, boards[currentMove]);
5786 }
5787
5788
5789 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5790 // All positions will have equal probability, but the current method will not provide a unique
5791 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5792 #define DARK 1
5793 #define LITE 2
5794 #define ANY 3
5795
5796 int squaresLeft[4];
5797 int piecesLeft[(int)BlackPawn];
5798 int seed, nrOfShuffles;
5799
5800 void
5801 GetPositionNumber ()
5802 {       // sets global variable seed
5803         int i;
5804
5805         seed = appData.defaultFrcPosition;
5806         if(seed < 0) { // randomize based on time for negative FRC position numbers
5807                 for(i=0; i<50; i++) seed += random();
5808                 seed = random() ^ random() >> 8 ^ random() << 8;
5809                 if(seed<0) seed = -seed;
5810         }
5811 }
5812
5813 int
5814 put (Board board, int pieceType, int rank, int n, int shade)
5815 // put the piece on the (n-1)-th empty squares of the given shade
5816 {
5817         int i;
5818
5819         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5820                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5821                         board[rank][i] = (ChessSquare) pieceType;
5822                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5823                         squaresLeft[ANY]--;
5824                         piecesLeft[pieceType]--;
5825                         return i;
5826                 }
5827         }
5828         return -1;
5829 }
5830
5831
5832 void
5833 AddOnePiece (Board board, int pieceType, int rank, int shade)
5834 // calculate where the next piece goes, (any empty square), and put it there
5835 {
5836         int i;
5837
5838         i = seed % squaresLeft[shade];
5839         nrOfShuffles *= squaresLeft[shade];
5840         seed /= squaresLeft[shade];
5841         put(board, pieceType, rank, i, shade);
5842 }
5843
5844 void
5845 AddTwoPieces (Board board, int pieceType, int rank)
5846 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5847 {
5848         int i, n=squaresLeft[ANY], j=n-1, k;
5849
5850         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5851         i = seed % k;  // pick one
5852         nrOfShuffles *= k;
5853         seed /= k;
5854         while(i >= j) i -= j--;
5855         j = n - 1 - j; i += j;
5856         put(board, pieceType, rank, j, ANY);
5857         put(board, pieceType, rank, i, ANY);
5858 }
5859
5860 void
5861 SetUpShuffle (Board board, int number)
5862 {
5863         int i, p, first=1;
5864
5865         GetPositionNumber(); nrOfShuffles = 1;
5866
5867         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5868         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5869         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5870
5871         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5872
5873         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5874             p = (int) board[0][i];
5875             if(p < (int) BlackPawn) piecesLeft[p] ++;
5876             board[0][i] = EmptySquare;
5877         }
5878
5879         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5880             // shuffles restricted to allow normal castling put KRR first
5881             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5882                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5883             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5884                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5885             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5886                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5887             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5888                 put(board, WhiteRook, 0, 0, ANY);
5889             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5890         }
5891
5892         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5893             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5894             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5895                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5896                 while(piecesLeft[p] >= 2) {
5897                     AddOnePiece(board, p, 0, LITE);
5898                     AddOnePiece(board, p, 0, DARK);
5899                 }
5900                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5901             }
5902
5903         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5904             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5905             // but we leave King and Rooks for last, to possibly obey FRC restriction
5906             if(p == (int)WhiteRook) continue;
5907             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5908             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5909         }
5910
5911         // now everything is placed, except perhaps King (Unicorn) and Rooks
5912
5913         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5914             // Last King gets castling rights
5915             while(piecesLeft[(int)WhiteUnicorn]) {
5916                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5917                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5918             }
5919
5920             while(piecesLeft[(int)WhiteKing]) {
5921                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5922                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5923             }
5924
5925
5926         } else {
5927             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5928             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5929         }
5930
5931         // Only Rooks can be left; simply place them all
5932         while(piecesLeft[(int)WhiteRook]) {
5933                 i = put(board, WhiteRook, 0, 0, ANY);
5934                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5935                         if(first) {
5936                                 first=0;
5937                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5938                         }
5939                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5940                 }
5941         }
5942         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5943             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5944         }
5945
5946         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5947 }
5948
5949 int
5950 ptclen (const char *s, char *escapes)
5951 {
5952     int n = 0;
5953     if(!*escapes) return strlen(s);
5954     while(*s) n += (*s != ':' && !strchr(escapes, *s)), s++;
5955     return n;
5956 }
5957
5958 int
5959 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
5960 /* [HGM] moved here from winboard.c because of its general usefulness */
5961 /*       Basically a safe strcpy that uses the last character as King */
5962 {
5963     int result = FALSE; int NrPieces;
5964
5965     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
5966                     && NrPieces >= 12 && !(NrPieces&1)) {
5967         int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
5968
5969         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5970         for( i=0; i<NrPieces/2-1; i++ ) {
5971             char *p;
5972             if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
5973             table[i] = map[j++];
5974             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
5975         }
5976         table[(int) WhiteKing]  = map[j++];
5977         for( i=0; i<NrPieces/2-1; i++ ) {
5978             char *p;
5979             if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
5980             table[WHITE_TO_BLACK i] = map[j++];
5981             if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i] += 64*(p - escapes + 1);
5982         }
5983         table[(int) BlackKing]  = map[j++];
5984
5985         result = TRUE;
5986     }
5987
5988     return result;
5989 }
5990
5991 int
5992 SetCharTable (unsigned char *table, const char * map)
5993 {
5994     return SetCharTableEsc(table, map, "");
5995 }
5996
5997 void
5998 Prelude (Board board)
5999 {       // [HGM] superchess: random selection of exo-pieces
6000         int i, j, k; ChessSquare p;
6001         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6002
6003         GetPositionNumber(); // use FRC position number
6004
6005         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6006             SetCharTable(pieceToChar, appData.pieceToCharTable);
6007             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6008                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6009         }
6010
6011         j = seed%4;                 seed /= 4;
6012         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6013         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6014         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6015         j = seed%3 + (seed%3 >= j); seed /= 3;
6016         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6017         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6018         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6019         j = seed%3;                 seed /= 3;
6020         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6021         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6022         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6023         j = seed%2 + (seed%2 >= j); seed /= 2;
6024         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6025         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6026         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6027         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6028         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6029         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6030         put(board, exoPieces[0],    0, 0, ANY);
6031         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6032 }
6033
6034 void
6035 InitPosition (int redraw)
6036 {
6037     ChessSquare (* pieces)[BOARD_FILES];
6038     int i, j, pawnRow=1, pieceRows=1, overrule,
6039     oldx = gameInfo.boardWidth,
6040     oldy = gameInfo.boardHeight,
6041     oldh = gameInfo.holdingsWidth;
6042     static int oldv;
6043
6044     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6045
6046     /* [AS] Initialize pv info list [HGM] and game status */
6047     {
6048         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6049             pvInfoList[i].depth = 0;
6050             boards[i][EP_STATUS] = EP_NONE;
6051             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6052         }
6053
6054         initialRulePlies = 0; /* 50-move counter start */
6055
6056         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6057         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6058     }
6059
6060
6061     /* [HGM] logic here is completely changed. In stead of full positions */
6062     /* the initialized data only consist of the two backranks. The switch */
6063     /* selects which one we will use, which is than copied to the Board   */
6064     /* initialPosition, which for the rest is initialized by Pawns and    */
6065     /* empty squares. This initial position is then copied to boards[0],  */
6066     /* possibly after shuffling, so that it remains available.            */
6067
6068     gameInfo.holdingsWidth = 0; /* default board sizes */
6069     gameInfo.boardWidth    = 8;
6070     gameInfo.boardHeight   = 8;
6071     gameInfo.holdingsSize  = 0;
6072     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6073     for(i=0; i<BOARD_FILES-6; i++)
6074       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6075     initialPosition[EP_STATUS] = EP_NONE;
6076     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6077     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6078     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6079          SetCharTable(pieceNickName, appData.pieceNickNames);
6080     else SetCharTable(pieceNickName, "............");
6081     pieces = FIDEArray;
6082
6083     switch (gameInfo.variant) {
6084     case VariantFischeRandom:
6085       shuffleOpenings = TRUE;
6086       appData.fischerCastling = TRUE;
6087     default:
6088       break;
6089     case VariantShatranj:
6090       pieces = ShatranjArray;
6091       nrCastlingRights = 0;
6092       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6093       break;
6094     case VariantMakruk:
6095       pieces = makrukArray;
6096       nrCastlingRights = 0;
6097       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6098       break;
6099     case VariantASEAN:
6100       pieces = aseanArray;
6101       nrCastlingRights = 0;
6102       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6103       break;
6104     case VariantTwoKings:
6105       pieces = twoKingsArray;
6106       break;
6107     case VariantGrand:
6108       pieces = GrandArray;
6109       nrCastlingRights = 0;
6110       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6111       gameInfo.boardWidth = 10;
6112       gameInfo.boardHeight = 10;
6113       gameInfo.holdingsSize = 7;
6114       break;
6115     case VariantCapaRandom:
6116       shuffleOpenings = TRUE;
6117       appData.fischerCastling = TRUE;
6118     case VariantCapablanca:
6119       pieces = CapablancaArray;
6120       gameInfo.boardWidth = 10;
6121       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6122       break;
6123     case VariantGothic:
6124       pieces = GothicArray;
6125       gameInfo.boardWidth = 10;
6126       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6127       break;
6128     case VariantSChess:
6129       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6130       gameInfo.holdingsSize = 7;
6131       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6132       break;
6133     case VariantJanus:
6134       pieces = JanusArray;
6135       gameInfo.boardWidth = 10;
6136       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6137       nrCastlingRights = 6;
6138         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6139         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6140         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6141         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6142         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6143         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6144       break;
6145     case VariantFalcon:
6146       pieces = FalconArray;
6147       gameInfo.boardWidth = 10;
6148       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6149       break;
6150     case VariantXiangqi:
6151       pieces = XiangqiArray;
6152       gameInfo.boardWidth  = 9;
6153       gameInfo.boardHeight = 10;
6154       nrCastlingRights = 0;
6155       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6156       break;
6157     case VariantShogi:
6158       pieces = ShogiArray;
6159       gameInfo.boardWidth  = 9;
6160       gameInfo.boardHeight = 9;
6161       gameInfo.holdingsSize = 7;
6162       nrCastlingRights = 0;
6163       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6164       break;
6165     case VariantChu:
6166       pieces = ChuArray; pieceRows = 3;
6167       gameInfo.boardWidth  = 12;
6168       gameInfo.boardHeight = 12;
6169       nrCastlingRights = 0;
6170       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN:+.++.++++++++++.+++++K"
6171                                    "p.brqsexogcathd.vmlifn:+.++.++++++++++.+++++k", SUFFIXES);
6172       break;
6173     case VariantCourier:
6174       pieces = CourierArray;
6175       gameInfo.boardWidth  = 12;
6176       nrCastlingRights = 0;
6177       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6178       break;
6179     case VariantKnightmate:
6180       pieces = KnightmateArray;
6181       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6182       break;
6183     case VariantSpartan:
6184       pieces = SpartanArray;
6185       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6186       break;
6187     case VariantLion:
6188       pieces = lionArray;
6189       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6190       break;
6191     case VariantChuChess:
6192       pieces = ChuChessArray;
6193       gameInfo.boardWidth = 10;
6194       gameInfo.boardHeight = 10;
6195       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6196       break;
6197     case VariantFairy:
6198       pieces = fairyArray;
6199       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6200       break;
6201     case VariantGreat:
6202       pieces = GreatArray;
6203       gameInfo.boardWidth = 10;
6204       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6205       gameInfo.holdingsSize = 8;
6206       break;
6207     case VariantSuper:
6208       pieces = FIDEArray;
6209       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6210       gameInfo.holdingsSize = 8;
6211       startedFromSetupPosition = TRUE;
6212       break;
6213     case VariantCrazyhouse:
6214     case VariantBughouse:
6215       pieces = FIDEArray;
6216       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6217       gameInfo.holdingsSize = 5;
6218       break;
6219     case VariantWildCastle:
6220       pieces = FIDEArray;
6221       /* !!?shuffle with kings guaranteed to be on d or e file */
6222       shuffleOpenings = 1;
6223       break;
6224     case VariantNoCastle:
6225       pieces = FIDEArray;
6226       nrCastlingRights = 0;
6227       /* !!?unconstrained back-rank shuffle */
6228       shuffleOpenings = 1;
6229       break;
6230     }
6231
6232     overrule = 0;
6233     if(appData.NrFiles >= 0) {
6234         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6235         gameInfo.boardWidth = appData.NrFiles;
6236     }
6237     if(appData.NrRanks >= 0) {
6238         gameInfo.boardHeight = appData.NrRanks;
6239     }
6240     if(appData.holdingsSize >= 0) {
6241         i = appData.holdingsSize;
6242         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6243         gameInfo.holdingsSize = i;
6244     }
6245     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6246     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6247         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6248
6249     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6250     if(pawnRow < 1) pawnRow = 1;
6251     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6252        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6253     if(gameInfo.variant == VariantChu) pawnRow = 3;
6254
6255     /* User pieceToChar list overrules defaults */
6256     if(appData.pieceToCharTable != NULL)
6257         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6258
6259     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6260
6261         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6262             s = (ChessSquare) 0; /* account holding counts in guard band */
6263         for( i=0; i<BOARD_HEIGHT; i++ )
6264             initialPosition[i][j] = s;
6265
6266         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6267         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6268         initialPosition[pawnRow][j] = WhitePawn;
6269         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6270         if(gameInfo.variant == VariantXiangqi) {
6271             if(j&1) {
6272                 initialPosition[pawnRow][j] =
6273                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6274                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6275                    initialPosition[2][j] = WhiteCannon;
6276                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6277                 }
6278             }
6279         }
6280         if(gameInfo.variant == VariantChu) {
6281              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6282                initialPosition[pawnRow+1][j] = WhiteCobra,
6283                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6284              for(i=1; i<pieceRows; i++) {
6285                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6286                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6287              }
6288         }
6289         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6290             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6291                initialPosition[0][j] = WhiteRook;
6292                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6293             }
6294         }
6295         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6296     }
6297     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6298     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6299
6300             j=BOARD_LEFT+1;
6301             initialPosition[1][j] = WhiteBishop;
6302             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6303             j=BOARD_RGHT-2;
6304             initialPosition[1][j] = WhiteRook;
6305             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6306     }
6307
6308     if( nrCastlingRights == -1) {
6309         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6310         /*       This sets default castling rights from none to normal corners   */
6311         /* Variants with other castling rights must set them themselves above    */
6312         nrCastlingRights = 6;
6313
6314         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6315         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6316         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6317         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6318         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6319         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6320      }
6321
6322      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6323      if(gameInfo.variant == VariantGreat) { // promotion commoners
6324         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6325         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6326         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6327         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6328      }
6329      if( gameInfo.variant == VariantSChess ) {
6330       initialPosition[1][0] = BlackMarshall;
6331       initialPosition[2][0] = BlackAngel;
6332       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6333       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6334       initialPosition[1][1] = initialPosition[2][1] =
6335       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6336      }
6337   if (appData.debugMode) {
6338     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6339   }
6340     if(shuffleOpenings) {
6341         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6342         startedFromSetupPosition = TRUE;
6343     }
6344     if(startedFromPositionFile) {
6345       /* [HGM] loadPos: use PositionFile for every new game */
6346       CopyBoard(initialPosition, filePosition);
6347       for(i=0; i<nrCastlingRights; i++)
6348           initialRights[i] = filePosition[CASTLING][i];
6349       startedFromSetupPosition = TRUE;
6350     }
6351
6352     CopyBoard(boards[0], initialPosition);
6353
6354     if(oldx != gameInfo.boardWidth ||
6355        oldy != gameInfo.boardHeight ||
6356        oldv != gameInfo.variant ||
6357        oldh != gameInfo.holdingsWidth
6358                                          )
6359             InitDrawingSizes(-2 ,0);
6360
6361     oldv = gameInfo.variant;
6362     if (redraw)
6363       DrawPosition(TRUE, boards[currentMove]);
6364 }
6365
6366 void
6367 SendBoard (ChessProgramState *cps, int moveNum)
6368 {
6369     char message[MSG_SIZ];
6370
6371     if (cps->useSetboard) {
6372       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6373       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6374       SendToProgram(message, cps);
6375       free(fen);
6376
6377     } else {
6378       ChessSquare *bp;
6379       int i, j, left=0, right=BOARD_WIDTH;
6380       /* Kludge to set black to move, avoiding the troublesome and now
6381        * deprecated "black" command.
6382        */
6383       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6384         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6385
6386       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6387
6388       SendToProgram("edit\n", cps);
6389       SendToProgram("#\n", cps);
6390       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6391         bp = &boards[moveNum][i][left];
6392         for (j = left; j < right; j++, bp++) {
6393           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6394           if ((int) *bp < (int) BlackPawn) {
6395             if(j == BOARD_RGHT+1)
6396                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6397             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6398             if(message[0] == '+' || message[0] == '~') {
6399               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6400                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6401                         AAA + j, ONE + i);
6402             }
6403             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6404                 message[1] = BOARD_RGHT   - 1 - j + '1';
6405                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6406             }
6407             SendToProgram(message, cps);
6408           }
6409         }
6410       }
6411
6412       SendToProgram("c\n", cps);
6413       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6414         bp = &boards[moveNum][i][left];
6415         for (j = left; j < right; j++, bp++) {
6416           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6417           if (((int) *bp != (int) EmptySquare)
6418               && ((int) *bp >= (int) BlackPawn)) {
6419             if(j == BOARD_LEFT-2)
6420                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6421             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6422                     AAA + j, ONE + i);
6423             if(message[0] == '+' || message[0] == '~') {
6424               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6425                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6426                         AAA + j, ONE + i);
6427             }
6428             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6429                 message[1] = BOARD_RGHT   - 1 - j + '1';
6430                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6431             }
6432             SendToProgram(message, cps);
6433           }
6434         }
6435       }
6436
6437       SendToProgram(".\n", cps);
6438     }
6439     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6440 }
6441
6442 char exclusionHeader[MSG_SIZ];
6443 int exCnt, excludePtr;
6444 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6445 static Exclusion excluTab[200];
6446 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6447
6448 static void
6449 WriteMap (int s)
6450 {
6451     int j;
6452     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6453     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6454 }
6455
6456 static void
6457 ClearMap ()
6458 {
6459     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6460     excludePtr = 24; exCnt = 0;
6461     WriteMap(0);
6462 }
6463
6464 static void
6465 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6466 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6467     char buf[2*MOVE_LEN], *p;
6468     Exclusion *e = excluTab;
6469     int i;
6470     for(i=0; i<exCnt; i++)
6471         if(e[i].ff == fromX && e[i].fr == fromY &&
6472            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6473     if(i == exCnt) { // was not in exclude list; add it
6474         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6475         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6476             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6477             return; // abort
6478         }
6479         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6480         excludePtr++; e[i].mark = excludePtr++;
6481         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6482         exCnt++;
6483     }
6484     exclusionHeader[e[i].mark] = state;
6485 }
6486
6487 static int
6488 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6489 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6490     char buf[MSG_SIZ];
6491     int j, k;
6492     ChessMove moveType;
6493     if((signed char)promoChar == -1) { // kludge to indicate best move
6494         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6495             return 1; // if unparsable, abort
6496     }
6497     // update exclusion map (resolving toggle by consulting existing state)
6498     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6499     j = k%8; k >>= 3;
6500     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6501     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6502          excludeMap[k] |=   1<<j;
6503     else excludeMap[k] &= ~(1<<j);
6504     // update header
6505     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6506     // inform engine
6507     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6508     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6509     SendToBoth(buf);
6510     return (state == '+');
6511 }
6512
6513 static void
6514 ExcludeClick (int index)
6515 {
6516     int i, j;
6517     Exclusion *e = excluTab;
6518     if(index < 25) { // none, best or tail clicked
6519         if(index < 13) { // none: include all
6520             WriteMap(0); // clear map
6521             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6522             SendToBoth("include all\n"); // and inform engine
6523         } else if(index > 18) { // tail
6524             if(exclusionHeader[19] == '-') { // tail was excluded
6525                 SendToBoth("include all\n");
6526                 WriteMap(0); // clear map completely
6527                 // now re-exclude selected moves
6528                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6529                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6530             } else { // tail was included or in mixed state
6531                 SendToBoth("exclude all\n");
6532                 WriteMap(0xFF); // fill map completely
6533                 // now re-include selected moves
6534                 j = 0; // count them
6535                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6536                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6537                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6538             }
6539         } else { // best
6540             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6541         }
6542     } else {
6543         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6544             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6545             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6546             break;
6547         }
6548     }
6549 }
6550
6551 ChessSquare
6552 DefaultPromoChoice (int white)
6553 {
6554     ChessSquare result;
6555     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6556        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6557         result = WhiteFerz; // no choice
6558     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6559         result= WhiteKing; // in Suicide Q is the last thing we want
6560     else if(gameInfo.variant == VariantSpartan)
6561         result = white ? WhiteQueen : WhiteAngel;
6562     else result = WhiteQueen;
6563     if(!white) result = WHITE_TO_BLACK result;
6564     return result;
6565 }
6566
6567 static int autoQueen; // [HGM] oneclick
6568
6569 int
6570 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6571 {
6572     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6573     /* [HGM] add Shogi promotions */
6574     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6575     ChessSquare piece, partner;
6576     ChessMove moveType;
6577     Boolean premove;
6578
6579     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6580     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6581
6582     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6583       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6584         return FALSE;
6585
6586     piece = boards[currentMove][fromY][fromX];
6587     if(gameInfo.variant == VariantChu) {
6588         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6589         promotionZoneSize = BOARD_HEIGHT/3;
6590         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6591     } else if(gameInfo.variant == VariantShogi) {
6592         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6593         highestPromotingPiece = (int)WhiteAlfil;
6594     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6595         promotionZoneSize = 3;
6596     }
6597
6598     // Treat Lance as Pawn when it is not representing Amazon or Lance
6599     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6600         if(piece == WhiteLance) piece = WhitePawn; else
6601         if(piece == BlackLance) piece = BlackPawn;
6602     }
6603
6604     // next weed out all moves that do not touch the promotion zone at all
6605     if((int)piece >= BlackPawn) {
6606         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6607              return FALSE;
6608         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6609         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6610     } else {
6611         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6612            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6613         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6614              return FALSE;
6615     }
6616
6617     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6618
6619     // weed out mandatory Shogi promotions
6620     if(gameInfo.variant == VariantShogi) {
6621         if(piece >= BlackPawn) {
6622             if(toY == 0 && piece == BlackPawn ||
6623                toY == 0 && piece == BlackQueen ||
6624                toY <= 1 && piece == BlackKnight) {
6625                 *promoChoice = '+';
6626                 return FALSE;
6627             }
6628         } else {
6629             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6630                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6631                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6632                 *promoChoice = '+';
6633                 return FALSE;
6634             }
6635         }
6636     }
6637
6638     // weed out obviously illegal Pawn moves
6639     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6640         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6641         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6642         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6643         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6644         // note we are not allowed to test for valid (non-)capture, due to premove
6645     }
6646
6647     // we either have a choice what to promote to, or (in Shogi) whether to promote
6648     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6649        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6650         ChessSquare p=BlackFerz;  // no choice
6651         while(p < EmptySquare) {  //but make sure we use piece that exists
6652             *promoChoice = PieceToChar(p++);
6653             if(*promoChoice != '.') break;
6654         }
6655         return FALSE;
6656     }
6657     // no sense asking what we must promote to if it is going to explode...
6658     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6659         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6660         return FALSE;
6661     }
6662     // give caller the default choice even if we will not make it
6663     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6664     partner = piece; // pieces can promote if the pieceToCharTable says so
6665     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6666     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6667     if(        sweepSelect && gameInfo.variant != VariantGreat
6668                            && gameInfo.variant != VariantGrand
6669                            && gameInfo.variant != VariantSuper) return FALSE;
6670     if(autoQueen) return FALSE; // predetermined
6671
6672     // suppress promotion popup on illegal moves that are not premoves
6673     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6674               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6675     if(appData.testLegality && !premove) {
6676         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6677                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6678         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6679         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6680             return FALSE;
6681     }
6682
6683     return TRUE;
6684 }
6685
6686 int
6687 InPalace (int row, int column)
6688 {   /* [HGM] for Xiangqi */
6689     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6690          column < (BOARD_WIDTH + 4)/2 &&
6691          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6692     return FALSE;
6693 }
6694
6695 int
6696 PieceForSquare (int x, int y)
6697 {
6698   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6699      return -1;
6700   else
6701      return boards[currentMove][y][x];
6702 }
6703
6704 int
6705 OKToStartUserMove (int x, int y)
6706 {
6707     ChessSquare from_piece;
6708     int white_piece;
6709
6710     if (matchMode) return FALSE;
6711     if (gameMode == EditPosition) return TRUE;
6712
6713     if (x >= 0 && y >= 0)
6714       from_piece = boards[currentMove][y][x];
6715     else
6716       from_piece = EmptySquare;
6717
6718     if (from_piece == EmptySquare) return FALSE;
6719
6720     white_piece = (int)from_piece >= (int)WhitePawn &&
6721       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6722
6723     switch (gameMode) {
6724       case AnalyzeFile:
6725       case TwoMachinesPlay:
6726       case EndOfGame:
6727         return FALSE;
6728
6729       case IcsObserving:
6730       case IcsIdle:
6731         return FALSE;
6732
6733       case MachinePlaysWhite:
6734       case IcsPlayingBlack:
6735         if (appData.zippyPlay) return FALSE;
6736         if (white_piece) {
6737             DisplayMoveError(_("You are playing Black"));
6738             return FALSE;
6739         }
6740         break;
6741
6742       case MachinePlaysBlack:
6743       case IcsPlayingWhite:
6744         if (appData.zippyPlay) return FALSE;
6745         if (!white_piece) {
6746             DisplayMoveError(_("You are playing White"));
6747             return FALSE;
6748         }
6749         break;
6750
6751       case PlayFromGameFile:
6752             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6753       case EditGame:
6754         if (!white_piece && WhiteOnMove(currentMove)) {
6755             DisplayMoveError(_("It is White's turn"));
6756             return FALSE;
6757         }
6758         if (white_piece && !WhiteOnMove(currentMove)) {
6759             DisplayMoveError(_("It is Black's turn"));
6760             return FALSE;
6761         }
6762         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6763             /* Editing correspondence game history */
6764             /* Could disallow this or prompt for confirmation */
6765             cmailOldMove = -1;
6766         }
6767         break;
6768
6769       case BeginningOfGame:
6770         if (appData.icsActive) return FALSE;
6771         if (!appData.noChessProgram) {
6772             if (!white_piece) {
6773                 DisplayMoveError(_("You are playing White"));
6774                 return FALSE;
6775             }
6776         }
6777         break;
6778
6779       case Training:
6780         if (!white_piece && WhiteOnMove(currentMove)) {
6781             DisplayMoveError(_("It is White's turn"));
6782             return FALSE;
6783         }
6784         if (white_piece && !WhiteOnMove(currentMove)) {
6785             DisplayMoveError(_("It is Black's turn"));
6786             return FALSE;
6787         }
6788         break;
6789
6790       default:
6791       case IcsExamining:
6792         break;
6793     }
6794     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6795         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6796         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6797         && gameMode != AnalyzeFile && gameMode != Training) {
6798         DisplayMoveError(_("Displayed position is not current"));
6799         return FALSE;
6800     }
6801     return TRUE;
6802 }
6803
6804 Boolean
6805 OnlyMove (int *x, int *y, Boolean captures)
6806 {
6807     DisambiguateClosure cl;
6808     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6809     switch(gameMode) {
6810       case MachinePlaysBlack:
6811       case IcsPlayingWhite:
6812       case BeginningOfGame:
6813         if(!WhiteOnMove(currentMove)) return FALSE;
6814         break;
6815       case MachinePlaysWhite:
6816       case IcsPlayingBlack:
6817         if(WhiteOnMove(currentMove)) return FALSE;
6818         break;
6819       case EditGame:
6820         break;
6821       default:
6822         return FALSE;
6823     }
6824     cl.pieceIn = EmptySquare;
6825     cl.rfIn = *y;
6826     cl.ffIn = *x;
6827     cl.rtIn = -1;
6828     cl.ftIn = -1;
6829     cl.promoCharIn = NULLCHAR;
6830     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6831     if( cl.kind == NormalMove ||
6832         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6833         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6834         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6835       fromX = cl.ff;
6836       fromY = cl.rf;
6837       *x = cl.ft;
6838       *y = cl.rt;
6839       return TRUE;
6840     }
6841     if(cl.kind != ImpossibleMove) return FALSE;
6842     cl.pieceIn = EmptySquare;
6843     cl.rfIn = -1;
6844     cl.ffIn = -1;
6845     cl.rtIn = *y;
6846     cl.ftIn = *x;
6847     cl.promoCharIn = NULLCHAR;
6848     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6849     if( cl.kind == NormalMove ||
6850         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6851         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6852         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6853       fromX = cl.ff;
6854       fromY = cl.rf;
6855       *x = cl.ft;
6856       *y = cl.rt;
6857       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6858       return TRUE;
6859     }
6860     return FALSE;
6861 }
6862
6863 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6864 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6865 int lastLoadGameUseList = FALSE;
6866 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6867 ChessMove lastLoadGameStart = EndOfFile;
6868 int doubleClick;
6869 Boolean addToBookFlag;
6870
6871 void
6872 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6873 {
6874     ChessMove moveType;
6875     ChessSquare pup;
6876     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6877
6878     /* Check if the user is playing in turn.  This is complicated because we
6879        let the user "pick up" a piece before it is his turn.  So the piece he
6880        tried to pick up may have been captured by the time he puts it down!
6881        Therefore we use the color the user is supposed to be playing in this
6882        test, not the color of the piece that is currently on the starting
6883        square---except in EditGame mode, where the user is playing both
6884        sides; fortunately there the capture race can't happen.  (It can
6885        now happen in IcsExamining mode, but that's just too bad.  The user
6886        will get a somewhat confusing message in that case.)
6887        */
6888
6889     switch (gameMode) {
6890       case AnalyzeFile:
6891       case TwoMachinesPlay:
6892       case EndOfGame:
6893       case IcsObserving:
6894       case IcsIdle:
6895         /* We switched into a game mode where moves are not accepted,
6896            perhaps while the mouse button was down. */
6897         return;
6898
6899       case MachinePlaysWhite:
6900         /* User is moving for Black */
6901         if (WhiteOnMove(currentMove)) {
6902             DisplayMoveError(_("It is White's turn"));
6903             return;
6904         }
6905         break;
6906
6907       case MachinePlaysBlack:
6908         /* User is moving for White */
6909         if (!WhiteOnMove(currentMove)) {
6910             DisplayMoveError(_("It is Black's turn"));
6911             return;
6912         }
6913         break;
6914
6915       case PlayFromGameFile:
6916             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6917       case EditGame:
6918       case IcsExamining:
6919       case BeginningOfGame:
6920       case AnalyzeMode:
6921       case Training:
6922         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6923         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6924             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6925             /* User is moving for Black */
6926             if (WhiteOnMove(currentMove)) {
6927                 DisplayMoveError(_("It is White's turn"));
6928                 return;
6929             }
6930         } else {
6931             /* User is moving for White */
6932             if (!WhiteOnMove(currentMove)) {
6933                 DisplayMoveError(_("It is Black's turn"));
6934                 return;
6935             }
6936         }
6937         break;
6938
6939       case IcsPlayingBlack:
6940         /* User is moving for Black */
6941         if (WhiteOnMove(currentMove)) {
6942             if (!appData.premove) {
6943                 DisplayMoveError(_("It is White's turn"));
6944             } else if (toX >= 0 && toY >= 0) {
6945                 premoveToX = toX;
6946                 premoveToY = toY;
6947                 premoveFromX = fromX;
6948                 premoveFromY = fromY;
6949                 premovePromoChar = promoChar;
6950                 gotPremove = 1;
6951                 if (appData.debugMode)
6952                     fprintf(debugFP, "Got premove: fromX %d,"
6953                             "fromY %d, toX %d, toY %d\n",
6954                             fromX, fromY, toX, toY);
6955             }
6956             return;
6957         }
6958         break;
6959
6960       case IcsPlayingWhite:
6961         /* User is moving for White */
6962         if (!WhiteOnMove(currentMove)) {
6963             if (!appData.premove) {
6964                 DisplayMoveError(_("It is Black's turn"));
6965             } else if (toX >= 0 && toY >= 0) {
6966                 premoveToX = toX;
6967                 premoveToY = toY;
6968                 premoveFromX = fromX;
6969                 premoveFromY = fromY;
6970                 premovePromoChar = promoChar;
6971                 gotPremove = 1;
6972                 if (appData.debugMode)
6973                     fprintf(debugFP, "Got premove: fromX %d,"
6974                             "fromY %d, toX %d, toY %d\n",
6975                             fromX, fromY, toX, toY);
6976             }
6977             return;
6978         }
6979         break;
6980
6981       default:
6982         break;
6983
6984       case EditPosition:
6985         /* EditPosition, empty square, or different color piece;
6986            click-click move is possible */
6987         if (toX == -2 || toY == -2) {
6988             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6989             DrawPosition(FALSE, boards[currentMove]);
6990             return;
6991         } else if (toX >= 0 && toY >= 0) {
6992             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6993                 ChessSquare q, p = boards[0][rf][ff];
6994                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6995                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6996                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6997                 if(PieceToChar(q) == '+') gatingPiece = p;
6998             }
6999             boards[0][toY][toX] = boards[0][fromY][fromX];
7000             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7001                 if(boards[0][fromY][0] != EmptySquare) {
7002                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7003                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7004                 }
7005             } else
7006             if(fromX == BOARD_RGHT+1) {
7007                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7008                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7009                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7010                 }
7011             } else
7012             boards[0][fromY][fromX] = gatingPiece;
7013             DrawPosition(FALSE, boards[currentMove]);
7014             return;
7015         }
7016         return;
7017     }
7018
7019     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7020     pup = boards[currentMove][toY][toX];
7021
7022     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7023     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7024          if( pup != EmptySquare ) return;
7025          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7026            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7027                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7028            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7029            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7030            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7031            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7032          fromY = DROP_RANK;
7033     }
7034
7035     /* [HGM] always test for legality, to get promotion info */
7036     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7037                                          fromY, fromX, toY, toX, promoChar);
7038
7039     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7040
7041     /* [HGM] but possibly ignore an IllegalMove result */
7042     if (appData.testLegality) {
7043         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7044             DisplayMoveError(_("Illegal move"));
7045             return;
7046         }
7047     }
7048
7049     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7050         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7051              ClearPremoveHighlights(); // was included
7052         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7053         return;
7054     }
7055
7056     if(addToBookFlag) { // adding moves to book
7057         char buf[MSG_SIZ], move[MSG_SIZ];
7058         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7059         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7060         AddBookMove(buf);
7061         addToBookFlag = FALSE;
7062         ClearHighlights();
7063         return;
7064     }
7065
7066     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7067 }
7068
7069 /* Common tail of UserMoveEvent and DropMenuEvent */
7070 int
7071 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7072 {
7073     char *bookHit = 0;
7074
7075     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7076         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7077         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7078         if(WhiteOnMove(currentMove)) {
7079             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7080         } else {
7081             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7082         }
7083     }
7084
7085     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7086        move type in caller when we know the move is a legal promotion */
7087     if(moveType == NormalMove && promoChar)
7088         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7089
7090     /* [HGM] <popupFix> The following if has been moved here from
7091        UserMoveEvent(). Because it seemed to belong here (why not allow
7092        piece drops in training games?), and because it can only be
7093        performed after it is known to what we promote. */
7094     if (gameMode == Training) {
7095       /* compare the move played on the board to the next move in the
7096        * game. If they match, display the move and the opponent's response.
7097        * If they don't match, display an error message.
7098        */
7099       int saveAnimate;
7100       Board testBoard;
7101       CopyBoard(testBoard, boards[currentMove]);
7102       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7103
7104       if (CompareBoards(testBoard, boards[currentMove+1])) {
7105         ForwardInner(currentMove+1);
7106
7107         /* Autoplay the opponent's response.
7108          * if appData.animate was TRUE when Training mode was entered,
7109          * the response will be animated.
7110          */
7111         saveAnimate = appData.animate;
7112         appData.animate = animateTraining;
7113         ForwardInner(currentMove+1);
7114         appData.animate = saveAnimate;
7115
7116         /* check for the end of the game */
7117         if (currentMove >= forwardMostMove) {
7118           gameMode = PlayFromGameFile;
7119           ModeHighlight();
7120           SetTrainingModeOff();
7121           DisplayInformation(_("End of game"));
7122         }
7123       } else {
7124         DisplayError(_("Incorrect move"), 0);
7125       }
7126       return 1;
7127     }
7128
7129   /* Ok, now we know that the move is good, so we can kill
7130      the previous line in Analysis Mode */
7131   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7132                                 && currentMove < forwardMostMove) {
7133     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7134     else forwardMostMove = currentMove;
7135   }
7136
7137   ClearMap();
7138
7139   /* If we need the chess program but it's dead, restart it */
7140   ResurrectChessProgram();
7141
7142   /* A user move restarts a paused game*/
7143   if (pausing)
7144     PauseEvent();
7145
7146   thinkOutput[0] = NULLCHAR;
7147
7148   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7149
7150   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7151     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7152     return 1;
7153   }
7154
7155   if (gameMode == BeginningOfGame) {
7156     if (appData.noChessProgram) {
7157       gameMode = EditGame;
7158       SetGameInfo();
7159     } else {
7160       char buf[MSG_SIZ];
7161       gameMode = MachinePlaysBlack;
7162       StartClocks();
7163       SetGameInfo();
7164       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7165       DisplayTitle(buf);
7166       if (first.sendName) {
7167         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7168         SendToProgram(buf, &first);
7169       }
7170       StartClocks();
7171     }
7172     ModeHighlight();
7173   }
7174
7175   /* Relay move to ICS or chess engine */
7176   if (appData.icsActive) {
7177     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7178         gameMode == IcsExamining) {
7179       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7180         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7181         SendToICS("draw ");
7182         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7183       }
7184       // also send plain move, in case ICS does not understand atomic claims
7185       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7186       ics_user_moved = 1;
7187     }
7188   } else {
7189     if (first.sendTime && (gameMode == BeginningOfGame ||
7190                            gameMode == MachinePlaysWhite ||
7191                            gameMode == MachinePlaysBlack)) {
7192       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7193     }
7194     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7195          // [HGM] book: if program might be playing, let it use book
7196         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7197         first.maybeThinking = TRUE;
7198     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7199         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7200         SendBoard(&first, currentMove+1);
7201         if(second.analyzing) {
7202             if(!second.useSetboard) SendToProgram("undo\n", &second);
7203             SendBoard(&second, currentMove+1);
7204         }
7205     } else {
7206         SendMoveToProgram(forwardMostMove-1, &first);
7207         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7208     }
7209     if (currentMove == cmailOldMove + 1) {
7210       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7211     }
7212   }
7213
7214   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7215
7216   switch (gameMode) {
7217   case EditGame:
7218     if(appData.testLegality)
7219     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7220     case MT_NONE:
7221     case MT_CHECK:
7222       break;
7223     case MT_CHECKMATE:
7224     case MT_STAINMATE:
7225       if (WhiteOnMove(currentMove)) {
7226         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7227       } else {
7228         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7229       }
7230       break;
7231     case MT_STALEMATE:
7232       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7233       break;
7234     }
7235     break;
7236
7237   case MachinePlaysBlack:
7238   case MachinePlaysWhite:
7239     /* disable certain menu options while machine is thinking */
7240     SetMachineThinkingEnables();
7241     break;
7242
7243   default:
7244     break;
7245   }
7246
7247   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7248   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7249
7250   if(bookHit) { // [HGM] book: simulate book reply
7251         static char bookMove[MSG_SIZ]; // a bit generous?
7252
7253         programStats.nodes = programStats.depth = programStats.time =
7254         programStats.score = programStats.got_only_move = 0;
7255         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7256
7257         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7258         strcat(bookMove, bookHit);
7259         HandleMachineMove(bookMove, &first);
7260   }
7261   return 1;
7262 }
7263
7264 void
7265 MarkByFEN(char *fen)
7266 {
7267         int r, f;
7268         if(!appData.markers || !appData.highlightDragging) return;
7269         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7270         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7271         while(*fen) {
7272             int s = 0;
7273             marker[r][f] = 0;
7274             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7275             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7276             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7277             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7278             if(*fen == 'T') marker[r][f++] = 0; else
7279             if(*fen == 'Y') marker[r][f++] = 1; else
7280             if(*fen == 'G') marker[r][f++] = 3; else
7281             if(*fen == 'B') marker[r][f++] = 4; else
7282             if(*fen == 'C') marker[r][f++] = 5; else
7283             if(*fen == 'M') marker[r][f++] = 6; else
7284             if(*fen == 'W') marker[r][f++] = 7; else
7285             if(*fen == 'D') marker[r][f++] = 8; else
7286             if(*fen == 'R') marker[r][f++] = 2; else {
7287                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7288               f += s; fen -= s>0;
7289             }
7290             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7291             if(r < 0) break;
7292             fen++;
7293         }
7294         DrawPosition(TRUE, NULL);
7295 }
7296
7297 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7298
7299 void
7300 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7301 {
7302     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7303     Markers *m = (Markers *) closure;
7304     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7305         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7306                          || kind == WhiteCapturesEnPassant
7307                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7308     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7309 }
7310
7311 static int hoverSavedValid;
7312
7313 void
7314 MarkTargetSquares (int clear)
7315 {
7316   int x, y, sum=0;
7317   if(clear) { // no reason to ever suppress clearing
7318     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7319     hoverSavedValid = 0;
7320     if(!sum) return; // nothing was cleared,no redraw needed
7321   } else {
7322     int capt = 0;
7323     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7324        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7325     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7326     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7327       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7328       if(capt)
7329       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7330     }
7331   }
7332   DrawPosition(FALSE, NULL);
7333 }
7334
7335 int
7336 Explode (Board board, int fromX, int fromY, int toX, int toY)
7337 {
7338     if(gameInfo.variant == VariantAtomic &&
7339        (board[toY][toX] != EmptySquare ||                     // capture?
7340         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7341                          board[fromY][fromX] == BlackPawn   )
7342       )) {
7343         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7344         return TRUE;
7345     }
7346     return FALSE;
7347 }
7348
7349 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7350
7351 int
7352 CanPromote (ChessSquare piece, int y)
7353 {
7354         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7355         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7356         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7357         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7358            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7359            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7360          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7361         return (piece == BlackPawn && y <= zone ||
7362                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7363                 piece == BlackLance && y <= zone ||
7364                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7365 }
7366
7367 void
7368 HoverEvent (int xPix, int yPix, int x, int y)
7369 {
7370         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7371         int r, f;
7372         if(!first.highlight) return;
7373         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7374         if(x == oldX && y == oldY) return; // only do something if we enter new square
7375         oldFromX = fromX; oldFromY = fromY;
7376         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7377           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7378             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7379           hoverSavedValid = 1;
7380         } else if(oldX != x || oldY != y) {
7381           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7382           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7383           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7384             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7385           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7386             char buf[MSG_SIZ];
7387             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7388             SendToProgram(buf, &first);
7389           }
7390           oldX = x; oldY = y;
7391 //        SetHighlights(fromX, fromY, x, y);
7392         }
7393 }
7394
7395 void ReportClick(char *action, int x, int y)
7396 {
7397         char buf[MSG_SIZ]; // Inform engine of what user does
7398         int r, f;
7399         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7400           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7401             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7402         if(!first.highlight || gameMode == EditPosition) return;
7403         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7404         SendToProgram(buf, &first);
7405 }
7406
7407 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7408
7409 void
7410 LeftClick (ClickType clickType, int xPix, int yPix)
7411 {
7412     int x, y;
7413     Boolean saveAnimate;
7414     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7415     char promoChoice = NULLCHAR;
7416     ChessSquare piece;
7417     static TimeMark lastClickTime, prevClickTime;
7418
7419     x = EventToSquare(xPix, BOARD_WIDTH);
7420     y = EventToSquare(yPix, BOARD_HEIGHT);
7421     if (!flipView && y >= 0) {
7422         y = BOARD_HEIGHT - 1 - y;
7423     }
7424     if (flipView && x >= 0) {
7425         x = BOARD_WIDTH - 1 - x;
7426     }
7427
7428     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7429         static int dummy;
7430         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7431         right = TRUE;
7432         return;
7433     }
7434
7435     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7436
7437     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7438
7439     if (clickType == Press) ErrorPopDown();
7440     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7441
7442     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7443         defaultPromoChoice = promoSweep;
7444         promoSweep = EmptySquare;   // terminate sweep
7445         promoDefaultAltered = TRUE;
7446         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7447     }
7448
7449     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7450         if(clickType == Release) return; // ignore upclick of click-click destination
7451         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7452         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7453         if(gameInfo.holdingsWidth &&
7454                 (WhiteOnMove(currentMove)
7455                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7456                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7457             // click in right holdings, for determining promotion piece
7458             ChessSquare p = boards[currentMove][y][x];
7459             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7460             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7461             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7462                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7463                 fromX = fromY = -1;
7464                 return;
7465             }
7466         }
7467         DrawPosition(FALSE, boards[currentMove]);
7468         return;
7469     }
7470
7471     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7472     if(clickType == Press
7473             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7474               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7475               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7476         return;
7477
7478     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7479         // could be static click on premove from-square: abort premove
7480         gotPremove = 0;
7481         ClearPremoveHighlights();
7482     }
7483
7484     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7485         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7486
7487     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7488         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7489                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7490         defaultPromoChoice = DefaultPromoChoice(side);
7491     }
7492
7493     autoQueen = appData.alwaysPromoteToQueen;
7494
7495     if (fromX == -1) {
7496       int originalY = y;
7497       gatingPiece = EmptySquare;
7498       if (clickType != Press) {
7499         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7500             DragPieceEnd(xPix, yPix); dragging = 0;
7501             DrawPosition(FALSE, NULL);
7502         }
7503         return;
7504       }
7505       doubleClick = FALSE;
7506       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7507         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7508       }
7509       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7510       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7511          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7512          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7513             /* First square */
7514             if (OKToStartUserMove(fromX, fromY)) {
7515                 second = 0;
7516                 ReportClick("lift", x, y);
7517                 MarkTargetSquares(0);
7518                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7519                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7520                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7521                     promoSweep = defaultPromoChoice;
7522                     selectFlag = 0; lastX = xPix; lastY = yPix;
7523                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7524                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7525                 }
7526                 if (appData.highlightDragging) {
7527                     SetHighlights(fromX, fromY, -1, -1);
7528                 } else {
7529                     ClearHighlights();
7530                 }
7531             } else fromX = fromY = -1;
7532             return;
7533         }
7534     }
7535 printf("to click %d,%d\n",x,y);
7536     /* fromX != -1 */
7537     if (clickType == Press && gameMode != EditPosition) {
7538         ChessSquare fromP;
7539         ChessSquare toP;
7540         int frc;
7541
7542         // ignore off-board to clicks
7543         if(y < 0 || x < 0) return;
7544
7545         /* Check if clicking again on the same color piece */
7546         fromP = boards[currentMove][fromY][fromX];
7547         toP = boards[currentMove][y][x];
7548         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7549         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7550             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7551            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7552              WhitePawn <= toP && toP <= WhiteKing &&
7553              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7554              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7555             (BlackPawn <= fromP && fromP <= BlackKing &&
7556              BlackPawn <= toP && toP <= BlackKing &&
7557              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7558              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7559             /* Clicked again on same color piece -- changed his mind */
7560             second = (x == fromX && y == fromY);
7561             killX = killY = -1;
7562             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7563                 second = FALSE; // first double-click rather than scond click
7564                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7565             }
7566             promoDefaultAltered = FALSE;
7567             MarkTargetSquares(1);
7568            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7569             if (appData.highlightDragging) {
7570                 SetHighlights(x, y, -1, -1);
7571             } else {
7572                 ClearHighlights();
7573             }
7574             if (OKToStartUserMove(x, y)) {
7575                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7576                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7577                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7578                  gatingPiece = boards[currentMove][fromY][fromX];
7579                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7580                 fromX = x;
7581                 fromY = y; dragging = 1;
7582                 if(!second) ReportClick("lift", x, y);
7583                 MarkTargetSquares(0);
7584                 DragPieceBegin(xPix, yPix, FALSE);
7585                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7586                     promoSweep = defaultPromoChoice;
7587                     selectFlag = 0; lastX = xPix; lastY = yPix;
7588                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7589                 }
7590             }
7591            }
7592            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7593            second = FALSE;
7594         }
7595         // ignore clicks on holdings
7596         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7597     }
7598 printf("A type=%d\n",clickType);
7599
7600     if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7601         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7602         return;
7603     }
7604
7605     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7606         DragPieceEnd(xPix, yPix); dragging = 0;
7607         if(clearFlag) {
7608             // a deferred attempt to click-click move an empty square on top of a piece
7609             boards[currentMove][y][x] = EmptySquare;
7610             ClearHighlights();
7611             DrawPosition(FALSE, boards[currentMove]);
7612             fromX = fromY = -1; clearFlag = 0;
7613             return;
7614         }
7615         if (appData.animateDragging) {
7616             /* Undo animation damage if any */
7617             DrawPosition(FALSE, NULL);
7618         }
7619         if (second) {
7620             /* Second up/down in same square; just abort move */
7621             second = 0;
7622             fromX = fromY = -1;
7623             gatingPiece = EmptySquare;
7624             MarkTargetSquares(1);
7625             ClearHighlights();
7626             gotPremove = 0;
7627             ClearPremoveHighlights();
7628         } else {
7629             /* First upclick in same square; start click-click mode */
7630             SetHighlights(x, y, -1, -1);
7631         }
7632         return;
7633     }
7634
7635     clearFlag = 0;
7636 printf("B\n");
7637     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7638        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7639         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7640         DisplayMessage(_("only marked squares are legal"),"");
7641         DrawPosition(TRUE, NULL);
7642         return; // ignore to-click
7643     }
7644 printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
7645     /* we now have a different from- and (possibly off-board) to-square */
7646     /* Completed move */
7647     if(!sweepSelecting) {
7648         toX = x;
7649         toY = y;
7650     }
7651
7652     piece = boards[currentMove][fromY][fromX];
7653
7654     saveAnimate = appData.animate;
7655     if (clickType == Press) {
7656         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7657         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7658             // must be Edit Position mode with empty-square selected
7659             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7660             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7661             return;
7662         }
7663         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7664             return;
7665         }
7666         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7667             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7668         } else
7669         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7670         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7671           if(appData.sweepSelect) {
7672             promoSweep = defaultPromoChoice;
7673             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7674             selectFlag = 0; lastX = xPix; lastY = yPix;
7675             Sweep(0); // Pawn that is going to promote: preview promotion piece
7676             sweepSelecting = 1;
7677             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7678             MarkTargetSquares(1);
7679           }
7680           return; // promo popup appears on up-click
7681         }
7682         /* Finish clickclick move */
7683         if (appData.animate || appData.highlightLastMove) {
7684             SetHighlights(fromX, fromY, toX, toY);
7685         } else {
7686             ClearHighlights();
7687         }
7688     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7689         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7690         if (appData.animate || appData.highlightLastMove) {
7691             SetHighlights(fromX, fromY, toX, toY);
7692         } else {
7693             ClearHighlights();
7694         }
7695     } else {
7696 #if 0
7697 // [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
7698         /* Finish drag move */
7699         if (appData.highlightLastMove) {
7700             SetHighlights(fromX, fromY, toX, toY);
7701         } else {
7702             ClearHighlights();
7703         }
7704 #endif
7705         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7706         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7707           dragging *= 2;            // flag button-less dragging if we are dragging
7708           MarkTargetSquares(1);
7709           if(x == killX && y == killY) killX = killY = -1; else {
7710             killX = x; killY = y;     //remeber this square as intermediate
7711             ReportClick("put", x, y); // and inform engine
7712             ReportClick("lift", x, y);
7713             MarkTargetSquares(0);
7714             return;
7715           }
7716         }
7717         DragPieceEnd(xPix, yPix); dragging = 0;
7718         /* Don't animate move and drag both */
7719         appData.animate = FALSE;
7720     }
7721
7722     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7723     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7724         ChessSquare piece = boards[currentMove][fromY][fromX];
7725         if(gameMode == EditPosition && piece != EmptySquare &&
7726            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7727             int n;
7728
7729             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7730                 n = PieceToNumber(piece - (int)BlackPawn);
7731                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7732                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7733                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7734             } else
7735             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7736                 n = PieceToNumber(piece);
7737                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7738                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7739                 boards[currentMove][n][BOARD_WIDTH-2]++;
7740             }
7741             boards[currentMove][fromY][fromX] = EmptySquare;
7742         }
7743         ClearHighlights();
7744         fromX = fromY = -1;
7745         MarkTargetSquares(1);
7746         DrawPosition(TRUE, boards[currentMove]);
7747         return;
7748     }
7749
7750     // off-board moves should not be highlighted
7751     if(x < 0 || y < 0) ClearHighlights();
7752     else ReportClick("put", x, y);
7753
7754     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7755
7756     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7757
7758     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7759         SetHighlights(fromX, fromY, toX, toY);
7760         MarkTargetSquares(1);
7761         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7762             // [HGM] super: promotion to captured piece selected from holdings
7763             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7764             promotionChoice = TRUE;
7765             // kludge follows to temporarily execute move on display, without promoting yet
7766             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7767             boards[currentMove][toY][toX] = p;
7768             DrawPosition(FALSE, boards[currentMove]);
7769             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7770             boards[currentMove][toY][toX] = q;
7771             DisplayMessage("Click in holdings to choose piece", "");
7772             return;
7773         }
7774         PromotionPopUp(promoChoice);
7775     } else {
7776         int oldMove = currentMove;
7777         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7778         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7779         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7780         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7781            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7782             DrawPosition(TRUE, boards[currentMove]);
7783         MarkTargetSquares(1);
7784         fromX = fromY = -1;
7785     }
7786     appData.animate = saveAnimate;
7787     if (appData.animate || appData.animateDragging) {
7788         /* Undo animation damage if needed */
7789         DrawPosition(FALSE, NULL);
7790     }
7791 }
7792
7793 int
7794 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7795 {   // front-end-free part taken out of PieceMenuPopup
7796     int whichMenu; int xSqr, ySqr;
7797
7798     if(seekGraphUp) { // [HGM] seekgraph
7799         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7800         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7801         return -2;
7802     }
7803
7804     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7805          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7806         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7807         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7808         if(action == Press)   {
7809             originalFlip = flipView;
7810             flipView = !flipView; // temporarily flip board to see game from partners perspective
7811             DrawPosition(TRUE, partnerBoard);
7812             DisplayMessage(partnerStatus, "");
7813             partnerUp = TRUE;
7814         } else if(action == Release) {
7815             flipView = originalFlip;
7816             DrawPosition(TRUE, boards[currentMove]);
7817             partnerUp = FALSE;
7818         }
7819         return -2;
7820     }
7821
7822     xSqr = EventToSquare(x, BOARD_WIDTH);
7823     ySqr = EventToSquare(y, BOARD_HEIGHT);
7824     if (action == Release) {
7825         if(pieceSweep != EmptySquare) {
7826             EditPositionMenuEvent(pieceSweep, toX, toY);
7827             pieceSweep = EmptySquare;
7828         } else UnLoadPV(); // [HGM] pv
7829     }
7830     if (action != Press) return -2; // return code to be ignored
7831     switch (gameMode) {
7832       case IcsExamining:
7833         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7834       case EditPosition:
7835         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7836         if (xSqr < 0 || ySqr < 0) return -1;
7837         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7838         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7839         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7840         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7841         NextPiece(0);
7842         return 2; // grab
7843       case IcsObserving:
7844         if(!appData.icsEngineAnalyze) return -1;
7845       case IcsPlayingWhite:
7846       case IcsPlayingBlack:
7847         if(!appData.zippyPlay) goto noZip;
7848       case AnalyzeMode:
7849       case AnalyzeFile:
7850       case MachinePlaysWhite:
7851       case MachinePlaysBlack:
7852       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7853         if (!appData.dropMenu) {
7854           LoadPV(x, y);
7855           return 2; // flag front-end to grab mouse events
7856         }
7857         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7858            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7859       case EditGame:
7860       noZip:
7861         if (xSqr < 0 || ySqr < 0) return -1;
7862         if (!appData.dropMenu || appData.testLegality &&
7863             gameInfo.variant != VariantBughouse &&
7864             gameInfo.variant != VariantCrazyhouse) return -1;
7865         whichMenu = 1; // drop menu
7866         break;
7867       default:
7868         return -1;
7869     }
7870
7871     if (((*fromX = xSqr) < 0) ||
7872         ((*fromY = ySqr) < 0)) {
7873         *fromX = *fromY = -1;
7874         return -1;
7875     }
7876     if (flipView)
7877       *fromX = BOARD_WIDTH - 1 - *fromX;
7878     else
7879       *fromY = BOARD_HEIGHT - 1 - *fromY;
7880
7881     return whichMenu;
7882 }
7883
7884 void
7885 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7886 {
7887 //    char * hint = lastHint;
7888     FrontEndProgramStats stats;
7889
7890     stats.which = cps == &first ? 0 : 1;
7891     stats.depth = cpstats->depth;
7892     stats.nodes = cpstats->nodes;
7893     stats.score = cpstats->score;
7894     stats.time = cpstats->time;
7895     stats.pv = cpstats->movelist;
7896     stats.hint = lastHint;
7897     stats.an_move_index = 0;
7898     stats.an_move_count = 0;
7899
7900     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7901         stats.hint = cpstats->move_name;
7902         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7903         stats.an_move_count = cpstats->nr_moves;
7904     }
7905
7906     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
7907
7908     SetProgramStats( &stats );
7909 }
7910
7911 void
7912 ClearEngineOutputPane (int which)
7913 {
7914     static FrontEndProgramStats dummyStats;
7915     dummyStats.which = which;
7916     dummyStats.pv = "#";
7917     SetProgramStats( &dummyStats );
7918 }
7919
7920 #define MAXPLAYERS 500
7921
7922 char *
7923 TourneyStandings (int display)
7924 {
7925     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7926     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7927     char result, *p, *names[MAXPLAYERS];
7928
7929     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7930         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7931     names[0] = p = strdup(appData.participants);
7932     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7933
7934     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7935
7936     while(result = appData.results[nr]) {
7937         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7938         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7939         wScore = bScore = 0;
7940         switch(result) {
7941           case '+': wScore = 2; break;
7942           case '-': bScore = 2; break;
7943           case '=': wScore = bScore = 1; break;
7944           case ' ':
7945           case '*': return strdup("busy"); // tourney not finished
7946         }
7947         score[w] += wScore;
7948         score[b] += bScore;
7949         games[w]++;
7950         games[b]++;
7951         nr++;
7952     }
7953     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7954     for(w=0; w<nPlayers; w++) {
7955         bScore = -1;
7956         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7957         ranking[w] = b; points[w] = bScore; score[b] = -2;
7958     }
7959     p = malloc(nPlayers*34+1);
7960     for(w=0; w<nPlayers && w<display; w++)
7961         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7962     free(names[0]);
7963     return p;
7964 }
7965
7966 void
7967 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7968 {       // count all piece types
7969         int p, f, r;
7970         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7971         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7972         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7973                 p = board[r][f];
7974                 pCnt[p]++;
7975                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7976                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7977                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7978                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7979                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7980                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7981         }
7982 }
7983
7984 int
7985 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7986 {
7987         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7988         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7989
7990         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7991         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7992         if(myPawns == 2 && nMine == 3) // KPP
7993             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7994         if(myPawns == 1 && nMine == 2) // KP
7995             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7996         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7997             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7998         if(myPawns) return FALSE;
7999         if(pCnt[WhiteRook+side])
8000             return pCnt[BlackRook-side] ||
8001                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8002                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8003                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8004         if(pCnt[WhiteCannon+side]) {
8005             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8006             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8007         }
8008         if(pCnt[WhiteKnight+side])
8009             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8010         return FALSE;
8011 }
8012
8013 int
8014 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8015 {
8016         VariantClass v = gameInfo.variant;
8017
8018         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8019         if(v == VariantShatranj) return TRUE; // always winnable through baring
8020         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8021         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8022
8023         if(v == VariantXiangqi) {
8024                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8025
8026                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8027                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8028                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8029                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8030                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8031                 if(stale) // we have at least one last-rank P plus perhaps C
8032                     return majors // KPKX
8033                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8034                 else // KCA*E*
8035                     return pCnt[WhiteFerz+side] // KCAK
8036                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8037                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8038                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8039
8040         } else if(v == VariantKnightmate) {
8041                 if(nMine == 1) return FALSE;
8042                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8043         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8044                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8045
8046                 if(nMine == 1) return FALSE; // bare King
8047                 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
8048                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8049                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8050                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8051                 if(pCnt[WhiteKnight+side])
8052                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8053                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8054                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8055                 if(nBishops)
8056                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8057                 if(pCnt[WhiteAlfil+side])
8058                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8059                 if(pCnt[WhiteWazir+side])
8060                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8061         }
8062
8063         return TRUE;
8064 }
8065
8066 int
8067 CompareWithRights (Board b1, Board b2)
8068 {
8069     int rights = 0;
8070     if(!CompareBoards(b1, b2)) return FALSE;
8071     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8072     /* compare castling rights */
8073     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8074            rights++; /* King lost rights, while rook still had them */
8075     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8076         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8077            rights++; /* but at least one rook lost them */
8078     }
8079     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8080            rights++;
8081     if( b1[CASTLING][5] != NoRights ) {
8082         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8083            rights++;
8084     }
8085     return rights == 0;
8086 }
8087
8088 int
8089 Adjudicate (ChessProgramState *cps)
8090 {       // [HGM] some adjudications useful with buggy engines
8091         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8092         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8093         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8094         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8095         int k, drop, count = 0; static int bare = 1;
8096         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8097         Boolean canAdjudicate = !appData.icsActive;
8098
8099         // most tests only when we understand the game, i.e. legality-checking on
8100             if( appData.testLegality )
8101             {   /* [HGM] Some more adjudications for obstinate engines */
8102                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8103                 static int moveCount = 6;
8104                 ChessMove result;
8105                 char *reason = NULL;
8106
8107                 /* Count what is on board. */
8108                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8109
8110                 /* Some material-based adjudications that have to be made before stalemate test */
8111                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8112                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8113                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8114                      if(canAdjudicate && appData.checkMates) {
8115                          if(engineOpponent)
8116                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8117                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8118                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8119                          return 1;
8120                      }
8121                 }
8122
8123                 /* Bare King in Shatranj (loses) or Losers (wins) */
8124                 if( nrW == 1 || nrB == 1) {
8125                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8126                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8127                      if(canAdjudicate && appData.checkMates) {
8128                          if(engineOpponent)
8129                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8130                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8131                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8132                          return 1;
8133                      }
8134                   } else
8135                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8136                   {    /* bare King */
8137                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8138                         if(canAdjudicate && appData.checkMates) {
8139                             /* but only adjudicate if adjudication enabled */
8140                             if(engineOpponent)
8141                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8142                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8143                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8144                             return 1;
8145                         }
8146                   }
8147                 } else bare = 1;
8148
8149
8150             // don't wait for engine to announce game end if we can judge ourselves
8151             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8152               case MT_CHECK:
8153                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8154                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8155                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8156                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8157                             checkCnt++;
8158                         if(checkCnt >= 2) {
8159                             reason = "Xboard adjudication: 3rd check";
8160                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8161                             break;
8162                         }
8163                     }
8164                 }
8165               case MT_NONE:
8166               default:
8167                 break;
8168               case MT_STEALMATE:
8169               case MT_STALEMATE:
8170               case MT_STAINMATE:
8171                 reason = "Xboard adjudication: Stalemate";
8172                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8173                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8174                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8175                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8176                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8177                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8178                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8179                                                                         EP_CHECKMATE : EP_WINS);
8180                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8181                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8182                 }
8183                 break;
8184               case MT_CHECKMATE:
8185                 reason = "Xboard adjudication: Checkmate";
8186                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8187                 if(gameInfo.variant == VariantShogi) {
8188                     if(forwardMostMove > backwardMostMove
8189                        && moveList[forwardMostMove-1][1] == '@'
8190                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8191                         reason = "XBoard adjudication: pawn-drop mate";
8192                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8193                     }
8194                 }
8195                 break;
8196             }
8197
8198                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8199                     case EP_STALEMATE:
8200                         result = GameIsDrawn; break;
8201                     case EP_CHECKMATE:
8202                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8203                     case EP_WINS:
8204                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8205                     default:
8206                         result = EndOfFile;
8207                 }
8208                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8209                     if(engineOpponent)
8210                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8211                     GameEnds( result, reason, GE_XBOARD );
8212                     return 1;
8213                 }
8214
8215                 /* Next absolutely insufficient mating material. */
8216                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8217                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8218                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8219
8220                      /* always flag draws, for judging claims */
8221                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8222
8223                      if(canAdjudicate && appData.materialDraws) {
8224                          /* but only adjudicate them if adjudication enabled */
8225                          if(engineOpponent) {
8226                            SendToProgram("force\n", engineOpponent); // suppress reply
8227                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8228                          }
8229                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8230                          return 1;
8231                      }
8232                 }
8233
8234                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8235                 if(gameInfo.variant == VariantXiangqi ?
8236                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8237                  : nrW + nrB == 4 &&
8238                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8239                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8240                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8241                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8242                    ) ) {
8243                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8244                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8245                           if(engineOpponent) {
8246                             SendToProgram("force\n", engineOpponent); // suppress reply
8247                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8248                           }
8249                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8250                           return 1;
8251                      }
8252                 } else moveCount = 6;
8253             }
8254
8255         // Repetition draws and 50-move rule can be applied independently of legality testing
8256
8257                 /* Check for rep-draws */
8258                 count = 0;
8259                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8260                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8261                 for(k = forwardMostMove-2;
8262                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8263                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8264                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8265                     k-=2)
8266                 {   int rights=0;
8267                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8268                         /* compare castling rights */
8269                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8270                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8271                                 rights++; /* King lost rights, while rook still had them */
8272                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8273                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8274                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8275                                    rights++; /* but at least one rook lost them */
8276                         }
8277                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8278                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8279                                 rights++;
8280                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8281                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8282                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8283                                    rights++;
8284                         }
8285                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8286                             && appData.drawRepeats > 1) {
8287                              /* adjudicate after user-specified nr of repeats */
8288                              int result = GameIsDrawn;
8289                              char *details = "XBoard adjudication: repetition draw";
8290                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8291                                 // [HGM] xiangqi: check for forbidden perpetuals
8292                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8293                                 for(m=forwardMostMove; m>k; m-=2) {
8294                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8295                                         ourPerpetual = 0; // the current mover did not always check
8296                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8297                                         hisPerpetual = 0; // the opponent did not always check
8298                                 }
8299                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8300                                                                         ourPerpetual, hisPerpetual);
8301                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8302                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8303                                     details = "Xboard adjudication: perpetual checking";
8304                                 } else
8305                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8306                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8307                                 } else
8308                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8309                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8310                                         result = BlackWins;
8311                                         details = "Xboard adjudication: repetition";
8312                                     }
8313                                 } else // it must be XQ
8314                                 // Now check for perpetual chases
8315                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8316                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8317                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8318                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8319                                         static char resdet[MSG_SIZ];
8320                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8321                                         details = resdet;
8322                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8323                                     } else
8324                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8325                                         break; // Abort repetition-checking loop.
8326                                 }
8327                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8328                              }
8329                              if(engineOpponent) {
8330                                SendToProgram("force\n", engineOpponent); // suppress reply
8331                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8332                              }
8333                              GameEnds( result, details, GE_XBOARD );
8334                              return 1;
8335                         }
8336                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8337                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8338                     }
8339                 }
8340
8341                 /* Now we test for 50-move draws. Determine ply count */
8342                 count = forwardMostMove;
8343                 /* look for last irreversble move */
8344                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8345                     count--;
8346                 /* if we hit starting position, add initial plies */
8347                 if( count == backwardMostMove )
8348                     count -= initialRulePlies;
8349                 count = forwardMostMove - count;
8350                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8351                         // adjust reversible move counter for checks in Xiangqi
8352                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8353                         if(i < backwardMostMove) i = backwardMostMove;
8354                         while(i <= forwardMostMove) {
8355                                 lastCheck = inCheck; // check evasion does not count
8356                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8357                                 if(inCheck || lastCheck) count--; // check does not count
8358                                 i++;
8359                         }
8360                 }
8361                 if( count >= 100)
8362                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8363                          /* this is used to judge if draw claims are legal */
8364                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8365                          if(engineOpponent) {
8366                            SendToProgram("force\n", engineOpponent); // suppress reply
8367                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8368                          }
8369                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8370                          return 1;
8371                 }
8372
8373                 /* if draw offer is pending, treat it as a draw claim
8374                  * when draw condition present, to allow engines a way to
8375                  * claim draws before making their move to avoid a race
8376                  * condition occurring after their move
8377                  */
8378                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8379                          char *p = NULL;
8380                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8381                              p = "Draw claim: 50-move rule";
8382                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8383                              p = "Draw claim: 3-fold repetition";
8384                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8385                              p = "Draw claim: insufficient mating material";
8386                          if( p != NULL && canAdjudicate) {
8387                              if(engineOpponent) {
8388                                SendToProgram("force\n", engineOpponent); // suppress reply
8389                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8390                              }
8391                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8392                              return 1;
8393                          }
8394                 }
8395
8396                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8397                     if(engineOpponent) {
8398                       SendToProgram("force\n", engineOpponent); // suppress reply
8399                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8400                     }
8401                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8402                     return 1;
8403                 }
8404         return 0;
8405 }
8406
8407 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8408 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8409 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8410
8411 static int
8412 BitbaseProbe ()
8413 {
8414     int pieces[10], squares[10], cnt=0, r, f, res;
8415     static int loaded;
8416     static PPROBE_EGBB probeBB;
8417     if(!appData.testLegality) return 10;
8418     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8419     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8420     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8421     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8422         ChessSquare piece = boards[forwardMostMove][r][f];
8423         int black = (piece >= BlackPawn);
8424         int type = piece - black*BlackPawn;
8425         if(piece == EmptySquare) continue;
8426         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8427         if(type == WhiteKing) type = WhiteQueen + 1;
8428         type = egbbCode[type];
8429         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8430         pieces[cnt] = type + black*6;
8431         if(++cnt > 5) return 11;
8432     }
8433     pieces[cnt] = squares[cnt] = 0;
8434     // probe EGBB
8435     if(loaded == 2) return 13; // loading failed before
8436     if(loaded == 0) {
8437         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8438         HMODULE lib;
8439         PLOAD_EGBB loadBB;
8440         loaded = 2; // prepare for failure
8441         if(!path) return 13; // no egbb installed
8442         strncpy(buf, path + 8, MSG_SIZ);
8443         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8444         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8445         lib = LoadLibrary(buf);
8446         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8447         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8448         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8449         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8450         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8451         loaded = 1; // success!
8452     }
8453     res = probeBB(forwardMostMove & 1, pieces, squares);
8454     return res > 0 ? 1 : res < 0 ? -1 : 0;
8455 }
8456
8457 char *
8458 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8459 {   // [HGM] book: this routine intercepts moves to simulate book replies
8460     char *bookHit = NULL;
8461
8462     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8463         char buf[MSG_SIZ];
8464         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8465         SendToProgram(buf, cps);
8466     }
8467     //first determine if the incoming move brings opponent into his book
8468     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8469         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8470     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8471     if(bookHit != NULL && !cps->bookSuspend) {
8472         // make sure opponent is not going to reply after receiving move to book position
8473         SendToProgram("force\n", cps);
8474         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8475     }
8476     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8477     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8478     // now arrange restart after book miss
8479     if(bookHit) {
8480         // after a book hit we never send 'go', and the code after the call to this routine
8481         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8482         char buf[MSG_SIZ], *move = bookHit;
8483         if(cps->useSAN) {
8484             int fromX, fromY, toX, toY;
8485             char promoChar;
8486             ChessMove moveType;
8487             move = buf + 30;
8488             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8489                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8490                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8491                                     PosFlags(forwardMostMove),
8492                                     fromY, fromX, toY, toX, promoChar, move);
8493             } else {
8494                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8495                 bookHit = NULL;
8496             }
8497         }
8498         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8499         SendToProgram(buf, cps);
8500         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8501     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8502         SendToProgram("go\n", cps);
8503         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8504     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8505         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8506             SendToProgram("go\n", cps);
8507         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8508     }
8509     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8510 }
8511
8512 int
8513 LoadError (char *errmess, ChessProgramState *cps)
8514 {   // unloads engine and switches back to -ncp mode if it was first
8515     if(cps->initDone) return FALSE;
8516     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8517     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8518     cps->pr = NoProc;
8519     if(cps == &first) {
8520         appData.noChessProgram = TRUE;
8521         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8522         gameMode = BeginningOfGame; ModeHighlight();
8523         SetNCPMode();
8524     }
8525     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8526     DisplayMessage("", ""); // erase waiting message
8527     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8528     return TRUE;
8529 }
8530
8531 char *savedMessage;
8532 ChessProgramState *savedState;
8533 void
8534 DeferredBookMove (void)
8535 {
8536         if(savedState->lastPing != savedState->lastPong)
8537                     ScheduleDelayedEvent(DeferredBookMove, 10);
8538         else
8539         HandleMachineMove(savedMessage, savedState);
8540 }
8541
8542 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8543 static ChessProgramState *stalledEngine;
8544 static char stashedInputMove[MSG_SIZ];
8545
8546 void
8547 HandleMachineMove (char *message, ChessProgramState *cps)
8548 {
8549     static char firstLeg[20];
8550     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8551     char realname[MSG_SIZ];
8552     int fromX, fromY, toX, toY;
8553     ChessMove moveType;
8554     char promoChar, roar;
8555     char *p, *pv=buf1;
8556     int machineWhite, oldError;
8557     char *bookHit;
8558
8559     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8560         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8561         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8562             DisplayError(_("Invalid pairing from pairing engine"), 0);
8563             return;
8564         }
8565         pairingReceived = 1;
8566         NextMatchGame();
8567         return; // Skim the pairing messages here.
8568     }
8569
8570     oldError = cps->userError; cps->userError = 0;
8571
8572 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8573     /*
8574      * Kludge to ignore BEL characters
8575      */
8576     while (*message == '\007') message++;
8577
8578     /*
8579      * [HGM] engine debug message: ignore lines starting with '#' character
8580      */
8581     if(cps->debug && *message == '#') return;
8582
8583     /*
8584      * Look for book output
8585      */
8586     if (cps == &first && bookRequested) {
8587         if (message[0] == '\t' || message[0] == ' ') {
8588             /* Part of the book output is here; append it */
8589             strcat(bookOutput, message);
8590             strcat(bookOutput, "  \n");
8591             return;
8592         } else if (bookOutput[0] != NULLCHAR) {
8593             /* All of book output has arrived; display it */
8594             char *p = bookOutput;
8595             while (*p != NULLCHAR) {
8596                 if (*p == '\t') *p = ' ';
8597                 p++;
8598             }
8599             DisplayInformation(bookOutput);
8600             bookRequested = FALSE;
8601             /* Fall through to parse the current output */
8602         }
8603     }
8604
8605     /*
8606      * Look for machine move.
8607      */
8608     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8609         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8610     {
8611         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8612             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8613             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8614             stalledEngine = cps;
8615             if(appData.ponderNextMove) { // bring opponent out of ponder
8616                 if(gameMode == TwoMachinesPlay) {
8617                     if(cps->other->pause)
8618                         PauseEngine(cps->other);
8619                     else
8620                         SendToProgram("easy\n", cps->other);
8621                 }
8622             }
8623             StopClocks();
8624             return;
8625         }
8626
8627         /* This method is only useful on engines that support ping */
8628         if (cps->lastPing != cps->lastPong) {
8629           if (gameMode == BeginningOfGame) {
8630             /* Extra move from before last new; ignore */
8631             if (appData.debugMode) {
8632                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8633             }
8634           } else {
8635             if (appData.debugMode) {
8636                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8637                         cps->which, gameMode);
8638             }
8639
8640             SendToProgram("undo\n", cps);
8641           }
8642           return;
8643         }
8644
8645         switch (gameMode) {
8646           case BeginningOfGame:
8647             /* Extra move from before last reset; ignore */
8648             if (appData.debugMode) {
8649                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8650             }
8651             return;
8652
8653           case EndOfGame:
8654           case IcsIdle:
8655           default:
8656             /* Extra move after we tried to stop.  The mode test is
8657                not a reliable way of detecting this problem, but it's
8658                the best we can do on engines that don't support ping.
8659             */
8660             if (appData.debugMode) {
8661                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8662                         cps->which, gameMode);
8663             }
8664             SendToProgram("undo\n", cps);
8665             return;
8666
8667           case MachinePlaysWhite:
8668           case IcsPlayingWhite:
8669             machineWhite = TRUE;
8670             break;
8671
8672           case MachinePlaysBlack:
8673           case IcsPlayingBlack:
8674             machineWhite = FALSE;
8675             break;
8676
8677           case TwoMachinesPlay:
8678             machineWhite = (cps->twoMachinesColor[0] == 'w');
8679             break;
8680         }
8681         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8682             if (appData.debugMode) {
8683                 fprintf(debugFP,
8684                         "Ignoring move out of turn by %s, gameMode %d"
8685                         ", forwardMost %d\n",
8686                         cps->which, gameMode, forwardMostMove);
8687             }
8688             return;
8689         }
8690
8691         if(cps->alphaRank) AlphaRank(machineMove, 4);
8692
8693         // [HGM] lion: (some very limited) support for Alien protocol
8694         killX = killY = -1;
8695         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8696             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8697             return;
8698         } else if(firstLeg[0]) { // there was a previous leg;
8699             // only support case where same piece makes two step
8700             char buf[20], *p = machineMove+1, *q = buf+1, f;
8701             safeStrCpy(buf, machineMove, 20);
8702             while(isdigit(*q)) q++; // find start of to-square
8703             safeStrCpy(machineMove, firstLeg, 20);
8704             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8705             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8706             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8707             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8708             firstLeg[0] = NULLCHAR;
8709         }
8710
8711         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8712                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8713             /* Machine move could not be parsed; ignore it. */
8714           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8715                     machineMove, _(cps->which));
8716             DisplayMoveError(buf1);
8717             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8718                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8719             if (gameMode == TwoMachinesPlay) {
8720               GameEnds(machineWhite ? BlackWins : WhiteWins,
8721                        buf1, GE_XBOARD);
8722             }
8723             return;
8724         }
8725
8726         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8727         /* So we have to redo legality test with true e.p. status here,  */
8728         /* to make sure an illegal e.p. capture does not slip through,   */
8729         /* to cause a forfeit on a justified illegal-move complaint      */
8730         /* of the opponent.                                              */
8731         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8732            ChessMove moveType;
8733            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8734                              fromY, fromX, toY, toX, promoChar);
8735             if(moveType == IllegalMove) {
8736               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8737                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8738                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8739                            buf1, GE_XBOARD);
8740                 return;
8741            } else if(!appData.fischerCastling)
8742            /* [HGM] Kludge to handle engines that send FRC-style castling
8743               when they shouldn't (like TSCP-Gothic) */
8744            switch(moveType) {
8745              case WhiteASideCastleFR:
8746              case BlackASideCastleFR:
8747                toX+=2;
8748                currentMoveString[2]++;
8749                break;
8750              case WhiteHSideCastleFR:
8751              case BlackHSideCastleFR:
8752                toX--;
8753                currentMoveString[2]--;
8754                break;
8755              default: ; // nothing to do, but suppresses warning of pedantic compilers
8756            }
8757         }
8758         hintRequested = FALSE;
8759         lastHint[0] = NULLCHAR;
8760         bookRequested = FALSE;
8761         /* Program may be pondering now */
8762         cps->maybeThinking = TRUE;
8763         if (cps->sendTime == 2) cps->sendTime = 1;
8764         if (cps->offeredDraw) cps->offeredDraw--;
8765
8766         /* [AS] Save move info*/
8767         pvInfoList[ forwardMostMove ].score = programStats.score;
8768         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8769         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8770
8771         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8772
8773         /* Test suites abort the 'game' after one move */
8774         if(*appData.finger) {
8775            static FILE *f;
8776            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8777            if(!f) f = fopen(appData.finger, "w");
8778            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8779            else { DisplayFatalError("Bad output file", errno, 0); return; }
8780            free(fen);
8781            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8782         }
8783
8784         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8785         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8786             int count = 0;
8787
8788             while( count < adjudicateLossPlies ) {
8789                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8790
8791                 if( count & 1 ) {
8792                     score = -score; /* Flip score for winning side */
8793                 }
8794
8795                 if( score > appData.adjudicateLossThreshold ) {
8796                     break;
8797                 }
8798
8799                 count++;
8800             }
8801
8802             if( count >= adjudicateLossPlies ) {
8803                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8804
8805                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8806                     "Xboard adjudication",
8807                     GE_XBOARD );
8808
8809                 return;
8810             }
8811         }
8812
8813         if(Adjudicate(cps)) {
8814             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8815             return; // [HGM] adjudicate: for all automatic game ends
8816         }
8817
8818 #if ZIPPY
8819         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8820             first.initDone) {
8821           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8822                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8823                 SendToICS("draw ");
8824                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8825           }
8826           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8827           ics_user_moved = 1;
8828           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8829                 char buf[3*MSG_SIZ];
8830
8831                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8832                         programStats.score / 100.,
8833                         programStats.depth,
8834                         programStats.time / 100.,
8835                         (unsigned int)programStats.nodes,
8836                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8837                         programStats.movelist);
8838                 SendToICS(buf);
8839           }
8840         }
8841 #endif
8842
8843         /* [AS] Clear stats for next move */
8844         ClearProgramStats();
8845         thinkOutput[0] = NULLCHAR;
8846         hiddenThinkOutputState = 0;
8847
8848         bookHit = NULL;
8849         if (gameMode == TwoMachinesPlay) {
8850             /* [HGM] relaying draw offers moved to after reception of move */
8851             /* and interpreting offer as claim if it brings draw condition */
8852             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8853                 SendToProgram("draw\n", cps->other);
8854             }
8855             if (cps->other->sendTime) {
8856                 SendTimeRemaining(cps->other,
8857                                   cps->other->twoMachinesColor[0] == 'w');
8858             }
8859             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8860             if (firstMove && !bookHit) {
8861                 firstMove = FALSE;
8862                 if (cps->other->useColors) {
8863                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8864                 }
8865                 SendToProgram("go\n", cps->other);
8866             }
8867             cps->other->maybeThinking = TRUE;
8868         }
8869
8870         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8871
8872         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8873
8874         if (!pausing && appData.ringBellAfterMoves) {
8875             if(!roar) RingBell();
8876         }
8877
8878         /*
8879          * Reenable menu items that were disabled while
8880          * machine was thinking
8881          */
8882         if (gameMode != TwoMachinesPlay)
8883             SetUserThinkingEnables();
8884
8885         // [HGM] book: after book hit opponent has received move and is now in force mode
8886         // force the book reply into it, and then fake that it outputted this move by jumping
8887         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8888         if(bookHit) {
8889                 static char bookMove[MSG_SIZ]; // a bit generous?
8890
8891                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8892                 strcat(bookMove, bookHit);
8893                 message = bookMove;
8894                 cps = cps->other;
8895                 programStats.nodes = programStats.depth = programStats.time =
8896                 programStats.score = programStats.got_only_move = 0;
8897                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8898
8899                 if(cps->lastPing != cps->lastPong) {
8900                     savedMessage = message; // args for deferred call
8901                     savedState = cps;
8902                     ScheduleDelayedEvent(DeferredBookMove, 10);
8903                     return;
8904                 }
8905                 goto FakeBookMove;
8906         }
8907
8908         return;
8909     }
8910
8911     /* Set special modes for chess engines.  Later something general
8912      *  could be added here; for now there is just one kludge feature,
8913      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8914      *  when "xboard" is given as an interactive command.
8915      */
8916     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8917         cps->useSigint = FALSE;
8918         cps->useSigterm = FALSE;
8919     }
8920     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8921       ParseFeatures(message+8, cps);
8922       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8923     }
8924
8925     if (!strncmp(message, "setup ", 6) && 
8926         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8927           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8928                                         ) { // [HGM] allow first engine to define opening position
8929       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8930       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8931       *buf = NULLCHAR;
8932       if(sscanf(message, "setup (%s", buf) == 1) {
8933         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
8934         ASSIGN(appData.pieceToCharTable, buf);
8935       }
8936       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8937       if(dummy >= 3) {
8938         while(message[s] && message[s++] != ' ');
8939         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8940            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8941             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8942             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8943           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8944           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
8945           startedFromSetupPosition = FALSE;
8946         }
8947       }
8948       if(startedFromSetupPosition) return;
8949       ParseFEN(boards[0], &dummy, message+s, FALSE);
8950       DrawPosition(TRUE, boards[0]);
8951       CopyBoard(initialPosition, boards[0]);
8952       startedFromSetupPosition = TRUE;
8953       return;
8954     }
8955     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8956       ChessSquare piece = WhitePawn;
8957       char *p=buf2, *q, *s = SUFFIXES, ID = *p;
8958       if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
8959       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
8960       piece += CharToPiece(ID) - WhitePawn;
8961       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8962       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8963       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8964       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8965       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8966       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8967                                                && gameInfo.variant != VariantGreat
8968                                                && gameInfo.variant != VariantFairy    ) return;
8969       if(piece < EmptySquare) {
8970         pieceDefs = TRUE;
8971         ASSIGN(pieceDesc[piece], buf1);
8972         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8973       }
8974       return;
8975     }
8976     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8977      * want this, I was asked to put it in, and obliged.
8978      */
8979     if (!strncmp(message, "setboard ", 9)) {
8980         Board initial_position;
8981
8982         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8983
8984         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8985             DisplayError(_("Bad FEN received from engine"), 0);
8986             return ;
8987         } else {
8988            Reset(TRUE, FALSE);
8989            CopyBoard(boards[0], initial_position);
8990            initialRulePlies = FENrulePlies;
8991            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8992            else gameMode = MachinePlaysBlack;
8993            DrawPosition(FALSE, boards[currentMove]);
8994         }
8995         return;
8996     }
8997
8998     /*
8999      * Look for communication commands
9000      */
9001     if (!strncmp(message, "telluser ", 9)) {
9002         if(message[9] == '\\' && message[10] == '\\')
9003             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9004         PlayTellSound();
9005         DisplayNote(message + 9);
9006         return;
9007     }
9008     if (!strncmp(message, "tellusererror ", 14)) {
9009         cps->userError = 1;
9010         if(message[14] == '\\' && message[15] == '\\')
9011             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9012         PlayTellSound();
9013         DisplayError(message + 14, 0);
9014         return;
9015     }
9016     if (!strncmp(message, "tellopponent ", 13)) {
9017       if (appData.icsActive) {
9018         if (loggedOn) {
9019           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9020           SendToICS(buf1);
9021         }
9022       } else {
9023         DisplayNote(message + 13);
9024       }
9025       return;
9026     }
9027     if (!strncmp(message, "tellothers ", 11)) {
9028       if (appData.icsActive) {
9029         if (loggedOn) {
9030           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9031           SendToICS(buf1);
9032         }
9033       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9034       return;
9035     }
9036     if (!strncmp(message, "tellall ", 8)) {
9037       if (appData.icsActive) {
9038         if (loggedOn) {
9039           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9040           SendToICS(buf1);
9041         }
9042       } else {
9043         DisplayNote(message + 8);
9044       }
9045       return;
9046     }
9047     if (strncmp(message, "warning", 7) == 0) {
9048         /* Undocumented feature, use tellusererror in new code */
9049         DisplayError(message, 0);
9050         return;
9051     }
9052     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9053         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9054         strcat(realname, " query");
9055         AskQuestion(realname, buf2, buf1, cps->pr);
9056         return;
9057     }
9058     /* Commands from the engine directly to ICS.  We don't allow these to be
9059      *  sent until we are logged on. Crafty kibitzes have been known to
9060      *  interfere with the login process.
9061      */
9062     if (loggedOn) {
9063         if (!strncmp(message, "tellics ", 8)) {
9064             SendToICS(message + 8);
9065             SendToICS("\n");
9066             return;
9067         }
9068         if (!strncmp(message, "tellicsnoalias ", 15)) {
9069             SendToICS(ics_prefix);
9070             SendToICS(message + 15);
9071             SendToICS("\n");
9072             return;
9073         }
9074         /* The following are for backward compatibility only */
9075         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9076             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9077             SendToICS(ics_prefix);
9078             SendToICS(message);
9079             SendToICS("\n");
9080             return;
9081         }
9082     }
9083     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9084         if(initPing == cps->lastPong) {
9085             if(gameInfo.variant == VariantUnknown) {
9086                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9087                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9088                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9089             }
9090             initPing = -1;
9091         }
9092         return;
9093     }
9094     if(!strncmp(message, "highlight ", 10)) {
9095         if((appData.testLegality || *engineVariant) && appData.markers) return;
9096         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9097         return;
9098     }
9099     if(!strncmp(message, "click ", 6)) {
9100         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9101         if(appData.testLegality || !appData.oneClick) return;
9102         sscanf(message+6, "%c%d%c", &f, &y, &c);
9103         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9104         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9105         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9106         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9107         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9108         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9109             LeftClick(Release, lastLeftX, lastLeftY);
9110         controlKey  = (c == ',');
9111         LeftClick(Press, x, y);
9112         LeftClick(Release, x, y);
9113         first.highlight = f;
9114         return;
9115     }
9116     /*
9117      * If the move is illegal, cancel it and redraw the board.
9118      * Also deal with other error cases.  Matching is rather loose
9119      * here to accommodate engines written before the spec.
9120      */
9121     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9122         strncmp(message, "Error", 5) == 0) {
9123         if (StrStr(message, "name") ||
9124             StrStr(message, "rating") || StrStr(message, "?") ||
9125             StrStr(message, "result") || StrStr(message, "board") ||
9126             StrStr(message, "bk") || StrStr(message, "computer") ||
9127             StrStr(message, "variant") || StrStr(message, "hint") ||
9128             StrStr(message, "random") || StrStr(message, "depth") ||
9129             StrStr(message, "accepted")) {
9130             return;
9131         }
9132         if (StrStr(message, "protover")) {
9133           /* Program is responding to input, so it's apparently done
9134              initializing, and this error message indicates it is
9135              protocol version 1.  So we don't need to wait any longer
9136              for it to initialize and send feature commands. */
9137           FeatureDone(cps, 1);
9138           cps->protocolVersion = 1;
9139           return;
9140         }
9141         cps->maybeThinking = FALSE;
9142
9143         if (StrStr(message, "draw")) {
9144             /* Program doesn't have "draw" command */
9145             cps->sendDrawOffers = 0;
9146             return;
9147         }
9148         if (cps->sendTime != 1 &&
9149             (StrStr(message, "time") || StrStr(message, "otim"))) {
9150           /* Program apparently doesn't have "time" or "otim" command */
9151           cps->sendTime = 0;
9152           return;
9153         }
9154         if (StrStr(message, "analyze")) {
9155             cps->analysisSupport = FALSE;
9156             cps->analyzing = FALSE;
9157 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9158             EditGameEvent(); // [HGM] try to preserve loaded game
9159             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9160             DisplayError(buf2, 0);
9161             return;
9162         }
9163         if (StrStr(message, "(no matching move)st")) {
9164           /* Special kludge for GNU Chess 4 only */
9165           cps->stKludge = TRUE;
9166           SendTimeControl(cps, movesPerSession, timeControl,
9167                           timeIncrement, appData.searchDepth,
9168                           searchTime);
9169           return;
9170         }
9171         if (StrStr(message, "(no matching move)sd")) {
9172           /* Special kludge for GNU Chess 4 only */
9173           cps->sdKludge = TRUE;
9174           SendTimeControl(cps, movesPerSession, timeControl,
9175                           timeIncrement, appData.searchDepth,
9176                           searchTime);
9177           return;
9178         }
9179         if (!StrStr(message, "llegal")) {
9180             return;
9181         }
9182         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9183             gameMode == IcsIdle) return;
9184         if (forwardMostMove <= backwardMostMove) return;
9185         if (pausing) PauseEvent();
9186       if(appData.forceIllegal) {
9187             // [HGM] illegal: machine refused move; force position after move into it
9188           SendToProgram("force\n", cps);
9189           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9190                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9191                 // when black is to move, while there might be nothing on a2 or black
9192                 // might already have the move. So send the board as if white has the move.
9193                 // But first we must change the stm of the engine, as it refused the last move
9194                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9195                 if(WhiteOnMove(forwardMostMove)) {
9196                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9197                     SendBoard(cps, forwardMostMove); // kludgeless board
9198                 } else {
9199                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9200                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9201                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9202                 }
9203           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9204             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9205                  gameMode == TwoMachinesPlay)
9206               SendToProgram("go\n", cps);
9207             return;
9208       } else
9209         if (gameMode == PlayFromGameFile) {
9210             /* Stop reading this game file */
9211             gameMode = EditGame;
9212             ModeHighlight();
9213         }
9214         /* [HGM] illegal-move claim should forfeit game when Xboard */
9215         /* only passes fully legal moves                            */
9216         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9217             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9218                                 "False illegal-move claim", GE_XBOARD );
9219             return; // do not take back move we tested as valid
9220         }
9221         currentMove = forwardMostMove-1;
9222         DisplayMove(currentMove-1); /* before DisplayMoveError */
9223         SwitchClocks(forwardMostMove-1); // [HGM] race
9224         DisplayBothClocks();
9225         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9226                 parseList[currentMove], _(cps->which));
9227         DisplayMoveError(buf1);
9228         DrawPosition(FALSE, boards[currentMove]);
9229
9230         SetUserThinkingEnables();
9231         return;
9232     }
9233     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9234         /* Program has a broken "time" command that
9235            outputs a string not ending in newline.
9236            Don't use it. */
9237         cps->sendTime = 0;
9238     }
9239     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9240         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9241             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9242     }
9243
9244     /*
9245      * If chess program startup fails, exit with an error message.
9246      * Attempts to recover here are futile. [HGM] Well, we try anyway
9247      */
9248     if ((StrStr(message, "unknown host") != NULL)
9249         || (StrStr(message, "No remote directory") != NULL)
9250         || (StrStr(message, "not found") != NULL)
9251         || (StrStr(message, "No such file") != NULL)
9252         || (StrStr(message, "can't alloc") != NULL)
9253         || (StrStr(message, "Permission denied") != NULL)) {
9254
9255         cps->maybeThinking = FALSE;
9256         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9257                 _(cps->which), cps->program, cps->host, message);
9258         RemoveInputSource(cps->isr);
9259         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9260             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9261             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9262         }
9263         return;
9264     }
9265
9266     /*
9267      * Look for hint output
9268      */
9269     if (sscanf(message, "Hint: %s", buf1) == 1) {
9270         if (cps == &first && hintRequested) {
9271             hintRequested = FALSE;
9272             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9273                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9274                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9275                                     PosFlags(forwardMostMove),
9276                                     fromY, fromX, toY, toX, promoChar, buf1);
9277                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9278                 DisplayInformation(buf2);
9279             } else {
9280                 /* Hint move could not be parsed!? */
9281               snprintf(buf2, sizeof(buf2),
9282                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9283                         buf1, _(cps->which));
9284                 DisplayError(buf2, 0);
9285             }
9286         } else {
9287           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9288         }
9289         return;
9290     }
9291
9292     /*
9293      * Ignore other messages if game is not in progress
9294      */
9295     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9296         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9297
9298     /*
9299      * look for win, lose, draw, or draw offer
9300      */
9301     if (strncmp(message, "1-0", 3) == 0) {
9302         char *p, *q, *r = "";
9303         p = strchr(message, '{');
9304         if (p) {
9305             q = strchr(p, '}');
9306             if (q) {
9307                 *q = NULLCHAR;
9308                 r = p + 1;
9309             }
9310         }
9311         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9312         return;
9313     } else if (strncmp(message, "0-1", 3) == 0) {
9314         char *p, *q, *r = "";
9315         p = strchr(message, '{');
9316         if (p) {
9317             q = strchr(p, '}');
9318             if (q) {
9319                 *q = NULLCHAR;
9320                 r = p + 1;
9321             }
9322         }
9323         /* Kludge for Arasan 4.1 bug */
9324         if (strcmp(r, "Black resigns") == 0) {
9325             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9326             return;
9327         }
9328         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9329         return;
9330     } else if (strncmp(message, "1/2", 3) == 0) {
9331         char *p, *q, *r = "";
9332         p = strchr(message, '{');
9333         if (p) {
9334             q = strchr(p, '}');
9335             if (q) {
9336                 *q = NULLCHAR;
9337                 r = p + 1;
9338             }
9339         }
9340
9341         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9342         return;
9343
9344     } else if (strncmp(message, "White resign", 12) == 0) {
9345         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9346         return;
9347     } else if (strncmp(message, "Black resign", 12) == 0) {
9348         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9349         return;
9350     } else if (strncmp(message, "White matches", 13) == 0 ||
9351                strncmp(message, "Black matches", 13) == 0   ) {
9352         /* [HGM] ignore GNUShogi noises */
9353         return;
9354     } else if (strncmp(message, "White", 5) == 0 &&
9355                message[5] != '(' &&
9356                StrStr(message, "Black") == NULL) {
9357         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9358         return;
9359     } else if (strncmp(message, "Black", 5) == 0 &&
9360                message[5] != '(') {
9361         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9362         return;
9363     } else if (strcmp(message, "resign") == 0 ||
9364                strcmp(message, "computer resigns") == 0) {
9365         switch (gameMode) {
9366           case MachinePlaysBlack:
9367           case IcsPlayingBlack:
9368             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9369             break;
9370           case MachinePlaysWhite:
9371           case IcsPlayingWhite:
9372             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9373             break;
9374           case TwoMachinesPlay:
9375             if (cps->twoMachinesColor[0] == 'w')
9376               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9377             else
9378               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9379             break;
9380           default:
9381             /* can't happen */
9382             break;
9383         }
9384         return;
9385     } else if (strncmp(message, "opponent mates", 14) == 0) {
9386         switch (gameMode) {
9387           case MachinePlaysBlack:
9388           case IcsPlayingBlack:
9389             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9390             break;
9391           case MachinePlaysWhite:
9392           case IcsPlayingWhite:
9393             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9394             break;
9395           case TwoMachinesPlay:
9396             if (cps->twoMachinesColor[0] == 'w')
9397               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9398             else
9399               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9400             break;
9401           default:
9402             /* can't happen */
9403             break;
9404         }
9405         return;
9406     } else if (strncmp(message, "computer mates", 14) == 0) {
9407         switch (gameMode) {
9408           case MachinePlaysBlack:
9409           case IcsPlayingBlack:
9410             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9411             break;
9412           case MachinePlaysWhite:
9413           case IcsPlayingWhite:
9414             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9415             break;
9416           case TwoMachinesPlay:
9417             if (cps->twoMachinesColor[0] == 'w')
9418               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9419             else
9420               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9421             break;
9422           default:
9423             /* can't happen */
9424             break;
9425         }
9426         return;
9427     } else if (strncmp(message, "checkmate", 9) == 0) {
9428         if (WhiteOnMove(forwardMostMove)) {
9429             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9430         } else {
9431             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9432         }
9433         return;
9434     } else if (strstr(message, "Draw") != NULL ||
9435                strstr(message, "game is a draw") != NULL) {
9436         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9437         return;
9438     } else if (strstr(message, "offer") != NULL &&
9439                strstr(message, "draw") != NULL) {
9440 #if ZIPPY
9441         if (appData.zippyPlay && first.initDone) {
9442             /* Relay offer to ICS */
9443             SendToICS(ics_prefix);
9444             SendToICS("draw\n");
9445         }
9446 #endif
9447         cps->offeredDraw = 2; /* valid until this engine moves twice */
9448         if (gameMode == TwoMachinesPlay) {
9449             if (cps->other->offeredDraw) {
9450                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9451             /* [HGM] in two-machine mode we delay relaying draw offer      */
9452             /* until after we also have move, to see if it is really claim */
9453             }
9454         } else if (gameMode == MachinePlaysWhite ||
9455                    gameMode == MachinePlaysBlack) {
9456           if (userOfferedDraw) {
9457             DisplayInformation(_("Machine accepts your draw offer"));
9458             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9459           } else {
9460             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9461           }
9462         }
9463     }
9464
9465
9466     /*
9467      * Look for thinking output
9468      */
9469     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9470           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9471                                 ) {
9472         int plylev, mvleft, mvtot, curscore, time;
9473         char mvname[MOVE_LEN];
9474         u64 nodes; // [DM]
9475         char plyext;
9476         int ignore = FALSE;
9477         int prefixHint = FALSE;
9478         mvname[0] = NULLCHAR;
9479
9480         switch (gameMode) {
9481           case MachinePlaysBlack:
9482           case IcsPlayingBlack:
9483             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9484             break;
9485           case MachinePlaysWhite:
9486           case IcsPlayingWhite:
9487             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9488             break;
9489           case AnalyzeMode:
9490           case AnalyzeFile:
9491             break;
9492           case IcsObserving: /* [DM] icsEngineAnalyze */
9493             if (!appData.icsEngineAnalyze) ignore = TRUE;
9494             break;
9495           case TwoMachinesPlay:
9496             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9497                 ignore = TRUE;
9498             }
9499             break;
9500           default:
9501             ignore = TRUE;
9502             break;
9503         }
9504
9505         if (!ignore) {
9506             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9507             buf1[0] = NULLCHAR;
9508             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9509                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9510
9511                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9512                     nodes += u64Const(0x100000000);
9513
9514                 if (plyext != ' ' && plyext != '\t') {
9515                     time *= 100;
9516                 }
9517
9518                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9519                 if( cps->scoreIsAbsolute &&
9520                     ( gameMode == MachinePlaysBlack ||
9521                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9522                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9523                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9524                      !WhiteOnMove(currentMove)
9525                     ) )
9526                 {
9527                     curscore = -curscore;
9528                 }
9529
9530                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9531
9532                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9533                         char buf[MSG_SIZ];
9534                         FILE *f;
9535                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9536                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9537                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9538                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9539                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9540                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9541                                 fclose(f);
9542                         }
9543                         else
9544                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9545                           DisplayError(_("failed writing PV"), 0);
9546                 }
9547
9548                 tempStats.depth = plylev;
9549                 tempStats.nodes = nodes;
9550                 tempStats.time = time;
9551                 tempStats.score = curscore;
9552                 tempStats.got_only_move = 0;
9553
9554                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9555                         int ticklen;
9556
9557                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9558                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9559                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9560                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9561                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9562                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9563                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9564                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9565                 }
9566
9567                 /* Buffer overflow protection */
9568                 if (pv[0] != NULLCHAR) {
9569                     if (strlen(pv) >= sizeof(tempStats.movelist)
9570                         && appData.debugMode) {
9571                         fprintf(debugFP,
9572                                 "PV is too long; using the first %u bytes.\n",
9573                                 (unsigned) sizeof(tempStats.movelist) - 1);
9574                     }
9575
9576                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9577                 } else {
9578                     sprintf(tempStats.movelist, " no PV\n");
9579                 }
9580
9581                 if (tempStats.seen_stat) {
9582                     tempStats.ok_to_send = 1;
9583                 }
9584
9585                 if (strchr(tempStats.movelist, '(') != NULL) {
9586                     tempStats.line_is_book = 1;
9587                     tempStats.nr_moves = 0;
9588                     tempStats.moves_left = 0;
9589                 } else {
9590                     tempStats.line_is_book = 0;
9591                 }
9592
9593                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9594                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9595
9596                 SendProgramStatsToFrontend( cps, &tempStats );
9597
9598                 /*
9599                     [AS] Protect the thinkOutput buffer from overflow... this
9600                     is only useful if buf1 hasn't overflowed first!
9601                 */
9602                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9603                          plylev,
9604                          (gameMode == TwoMachinesPlay ?
9605                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9606                          ((double) curscore) / 100.0,
9607                          prefixHint ? lastHint : "",
9608                          prefixHint ? " " : "" );
9609
9610                 if( buf1[0] != NULLCHAR ) {
9611                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9612
9613                     if( strlen(pv) > max_len ) {
9614                         if( appData.debugMode) {
9615                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9616                         }
9617                         pv[max_len+1] = '\0';
9618                     }
9619
9620                     strcat( thinkOutput, pv);
9621                 }
9622
9623                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9624                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9625                     DisplayMove(currentMove - 1);
9626                 }
9627                 return;
9628
9629             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9630                 /* crafty (9.25+) says "(only move) <move>"
9631                  * if there is only 1 legal move
9632                  */
9633                 sscanf(p, "(only move) %s", buf1);
9634                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9635                 sprintf(programStats.movelist, "%s (only move)", buf1);
9636                 programStats.depth = 1;
9637                 programStats.nr_moves = 1;
9638                 programStats.moves_left = 1;
9639                 programStats.nodes = 1;
9640                 programStats.time = 1;
9641                 programStats.got_only_move = 1;
9642
9643                 /* Not really, but we also use this member to
9644                    mean "line isn't going to change" (Crafty
9645                    isn't searching, so stats won't change) */
9646                 programStats.line_is_book = 1;
9647
9648                 SendProgramStatsToFrontend( cps, &programStats );
9649
9650                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9651                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9652                     DisplayMove(currentMove - 1);
9653                 }
9654                 return;
9655             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9656                               &time, &nodes, &plylev, &mvleft,
9657                               &mvtot, mvname) >= 5) {
9658                 /* The stat01: line is from Crafty (9.29+) in response
9659                    to the "." command */
9660                 programStats.seen_stat = 1;
9661                 cps->maybeThinking = TRUE;
9662
9663                 if (programStats.got_only_move || !appData.periodicUpdates)
9664                   return;
9665
9666                 programStats.depth = plylev;
9667                 programStats.time = time;
9668                 programStats.nodes = nodes;
9669                 programStats.moves_left = mvleft;
9670                 programStats.nr_moves = mvtot;
9671                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9672                 programStats.ok_to_send = 1;
9673                 programStats.movelist[0] = '\0';
9674
9675                 SendProgramStatsToFrontend( cps, &programStats );
9676
9677                 return;
9678
9679             } else if (strncmp(message,"++",2) == 0) {
9680                 /* Crafty 9.29+ outputs this */
9681                 programStats.got_fail = 2;
9682                 return;
9683
9684             } else if (strncmp(message,"--",2) == 0) {
9685                 /* Crafty 9.29+ outputs this */
9686                 programStats.got_fail = 1;
9687                 return;
9688
9689             } else if (thinkOutput[0] != NULLCHAR &&
9690                        strncmp(message, "    ", 4) == 0) {
9691                 unsigned message_len;
9692
9693                 p = message;
9694                 while (*p && *p == ' ') p++;
9695
9696                 message_len = strlen( p );
9697
9698                 /* [AS] Avoid buffer overflow */
9699                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9700                     strcat(thinkOutput, " ");
9701                     strcat(thinkOutput, p);
9702                 }
9703
9704                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9705                     strcat(programStats.movelist, " ");
9706                     strcat(programStats.movelist, p);
9707                 }
9708
9709                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9710                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9711                     DisplayMove(currentMove - 1);
9712                 }
9713                 return;
9714             }
9715         }
9716         else {
9717             buf1[0] = NULLCHAR;
9718
9719             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9720                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9721             {
9722                 ChessProgramStats cpstats;
9723
9724                 if (plyext != ' ' && plyext != '\t') {
9725                     time *= 100;
9726                 }
9727
9728                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9729                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9730                     curscore = -curscore;
9731                 }
9732
9733                 cpstats.depth = plylev;
9734                 cpstats.nodes = nodes;
9735                 cpstats.time = time;
9736                 cpstats.score = curscore;
9737                 cpstats.got_only_move = 0;
9738                 cpstats.movelist[0] = '\0';
9739
9740                 if (buf1[0] != NULLCHAR) {
9741                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9742                 }
9743
9744                 cpstats.ok_to_send = 0;
9745                 cpstats.line_is_book = 0;
9746                 cpstats.nr_moves = 0;
9747                 cpstats.moves_left = 0;
9748
9749                 SendProgramStatsToFrontend( cps, &cpstats );
9750             }
9751         }
9752     }
9753 }
9754
9755
9756 /* Parse a game score from the character string "game", and
9757    record it as the history of the current game.  The game
9758    score is NOT assumed to start from the standard position.
9759    The display is not updated in any way.
9760    */
9761 void
9762 ParseGameHistory (char *game)
9763 {
9764     ChessMove moveType;
9765     int fromX, fromY, toX, toY, boardIndex;
9766     char promoChar;
9767     char *p, *q;
9768     char buf[MSG_SIZ];
9769
9770     if (appData.debugMode)
9771       fprintf(debugFP, "Parsing game history: %s\n", game);
9772
9773     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9774     gameInfo.site = StrSave(appData.icsHost);
9775     gameInfo.date = PGNDate();
9776     gameInfo.round = StrSave("-");
9777
9778     /* Parse out names of players */
9779     while (*game == ' ') game++;
9780     p = buf;
9781     while (*game != ' ') *p++ = *game++;
9782     *p = NULLCHAR;
9783     gameInfo.white = StrSave(buf);
9784     while (*game == ' ') game++;
9785     p = buf;
9786     while (*game != ' ' && *game != '\n') *p++ = *game++;
9787     *p = NULLCHAR;
9788     gameInfo.black = StrSave(buf);
9789
9790     /* Parse moves */
9791     boardIndex = blackPlaysFirst ? 1 : 0;
9792     yynewstr(game);
9793     for (;;) {
9794         yyboardindex = boardIndex;
9795         moveType = (ChessMove) Myylex();
9796         switch (moveType) {
9797           case IllegalMove:             /* maybe suicide chess, etc. */
9798   if (appData.debugMode) {
9799     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9800     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9801     setbuf(debugFP, NULL);
9802   }
9803           case WhitePromotion:
9804           case BlackPromotion:
9805           case WhiteNonPromotion:
9806           case BlackNonPromotion:
9807           case NormalMove:
9808           case FirstLeg:
9809           case WhiteCapturesEnPassant:
9810           case BlackCapturesEnPassant:
9811           case WhiteKingSideCastle:
9812           case WhiteQueenSideCastle:
9813           case BlackKingSideCastle:
9814           case BlackQueenSideCastle:
9815           case WhiteKingSideCastleWild:
9816           case WhiteQueenSideCastleWild:
9817           case BlackKingSideCastleWild:
9818           case BlackQueenSideCastleWild:
9819           /* PUSH Fabien */
9820           case WhiteHSideCastleFR:
9821           case WhiteASideCastleFR:
9822           case BlackHSideCastleFR:
9823           case BlackASideCastleFR:
9824           /* POP Fabien */
9825             fromX = currentMoveString[0] - AAA;
9826             fromY = currentMoveString[1] - ONE;
9827             toX = currentMoveString[2] - AAA;
9828             toY = currentMoveString[3] - ONE;
9829             promoChar = currentMoveString[4];
9830             break;
9831           case WhiteDrop:
9832           case BlackDrop:
9833             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9834             fromX = moveType == WhiteDrop ?
9835               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9836             (int) CharToPiece(ToLower(currentMoveString[0]));
9837             fromY = DROP_RANK;
9838             toX = currentMoveString[2] - AAA;
9839             toY = currentMoveString[3] - ONE;
9840             promoChar = NULLCHAR;
9841             break;
9842           case AmbiguousMove:
9843             /* bug? */
9844             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9845   if (appData.debugMode) {
9846     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9847     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9848     setbuf(debugFP, NULL);
9849   }
9850             DisplayError(buf, 0);
9851             return;
9852           case ImpossibleMove:
9853             /* bug? */
9854             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9855   if (appData.debugMode) {
9856     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9857     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9858     setbuf(debugFP, NULL);
9859   }
9860             DisplayError(buf, 0);
9861             return;
9862           case EndOfFile:
9863             if (boardIndex < backwardMostMove) {
9864                 /* Oops, gap.  How did that happen? */
9865                 DisplayError(_("Gap in move list"), 0);
9866                 return;
9867             }
9868             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9869             if (boardIndex > forwardMostMove) {
9870                 forwardMostMove = boardIndex;
9871             }
9872             return;
9873           case ElapsedTime:
9874             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9875                 strcat(parseList[boardIndex-1], " ");
9876                 strcat(parseList[boardIndex-1], yy_text);
9877             }
9878             continue;
9879           case Comment:
9880           case PGNTag:
9881           case NAG:
9882           default:
9883             /* ignore */
9884             continue;
9885           case WhiteWins:
9886           case BlackWins:
9887           case GameIsDrawn:
9888           case GameUnfinished:
9889             if (gameMode == IcsExamining) {
9890                 if (boardIndex < backwardMostMove) {
9891                     /* Oops, gap.  How did that happen? */
9892                     return;
9893                 }
9894                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9895                 return;
9896             }
9897             gameInfo.result = moveType;
9898             p = strchr(yy_text, '{');
9899             if (p == NULL) p = strchr(yy_text, '(');
9900             if (p == NULL) {
9901                 p = yy_text;
9902                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9903             } else {
9904                 q = strchr(p, *p == '{' ? '}' : ')');
9905                 if (q != NULL) *q = NULLCHAR;
9906                 p++;
9907             }
9908             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9909             gameInfo.resultDetails = StrSave(p);
9910             continue;
9911         }
9912         if (boardIndex >= forwardMostMove &&
9913             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9914             backwardMostMove = blackPlaysFirst ? 1 : 0;
9915             return;
9916         }
9917         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9918                                  fromY, fromX, toY, toX, promoChar,
9919                                  parseList[boardIndex]);
9920         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9921         /* currentMoveString is set as a side-effect of yylex */
9922         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9923         strcat(moveList[boardIndex], "\n");
9924         boardIndex++;
9925         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9926         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9927           case MT_NONE:
9928           case MT_STALEMATE:
9929           default:
9930             break;
9931           case MT_CHECK:
9932             if(!IS_SHOGI(gameInfo.variant))
9933                 strcat(parseList[boardIndex - 1], "+");
9934             break;
9935           case MT_CHECKMATE:
9936           case MT_STAINMATE:
9937             strcat(parseList[boardIndex - 1], "#");
9938             break;
9939         }
9940     }
9941 }
9942
9943
9944 /* Apply a move to the given board  */
9945 void
9946 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9947 {
9948   ChessSquare captured = board[toY][toX], piece, pawn, king, killed; int p, rookX, oldEP, epRank, berolina = 0;
9949   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9950
9951     /* [HGM] compute & store e.p. status and castling rights for new position */
9952     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9953
9954       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9955       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
9956       board[EP_STATUS] = EP_NONE;
9957       board[EP_FILE] = board[EP_RANK] = 100;
9958
9959   if (fromY == DROP_RANK) {
9960         /* must be first */
9961         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9962             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9963             return;
9964         }
9965         piece = board[toY][toX] = (ChessSquare) fromX;
9966   } else {
9967 //      ChessSquare victim;
9968       int i;
9969
9970       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9971 //           victim = board[killY][killX],
9972            killed = board[killY][killX],
9973            board[killY][killX] = EmptySquare,
9974            board[EP_STATUS] = EP_CAPTURE;
9975
9976       if( board[toY][toX] != EmptySquare ) {
9977            board[EP_STATUS] = EP_CAPTURE;
9978            if( (fromX != toX || fromY != toY) && // not igui!
9979                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9980                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9981                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9982            }
9983       }
9984
9985       pawn = board[fromY][fromX];
9986       if( pawn == WhiteLance || pawn == BlackLance ) {
9987            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
9988                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
9989                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
9990            }
9991       }
9992       if( pawn == WhitePawn ) {
9993            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9994                board[EP_STATUS] = EP_PAWN_MOVE;
9995            if( toY-fromY>=2) {
9996                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
9997                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9998                         gameInfo.variant != VariantBerolina || toX < fromX)
9999                       board[EP_STATUS] = toX | berolina;
10000                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10001                         gameInfo.variant != VariantBerolina || toX > fromX)
10002                       board[EP_STATUS] = toX;
10003            }
10004       } else
10005       if( pawn == BlackPawn ) {
10006            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10007                board[EP_STATUS] = EP_PAWN_MOVE;
10008            if( toY-fromY<= -2) {
10009                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10010                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10011                         gameInfo.variant != VariantBerolina || toX < fromX)
10012                       board[EP_STATUS] = toX | berolina;
10013                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10014                         gameInfo.variant != VariantBerolina || toX > fromX)
10015                       board[EP_STATUS] = toX;
10016            }
10017        }
10018
10019        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10020        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10021        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10022        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10023
10024        for(i=0; i<nrCastlingRights; i++) {
10025            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10026               board[CASTLING][i] == toX   && castlingRank[i] == toY
10027              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10028        }
10029
10030        if(gameInfo.variant == VariantSChess) { // update virginity
10031            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10032            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10033            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10034            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10035        }
10036
10037      if (fromX == toX && fromY == toY) return;
10038
10039      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10040      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10041      if(gameInfo.variant == VariantKnightmate)
10042          king += (int) WhiteUnicorn - (int) WhiteKing;
10043
10044     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10045        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10046         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10047         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10048         board[EP_STATUS] = EP_NONE; // capture was fake!
10049     } else
10050     /* Code added by Tord: */
10051     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10052     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10053         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10054       board[EP_STATUS] = EP_NONE; // capture was fake!
10055       board[fromY][fromX] = EmptySquare;
10056       board[toY][toX] = EmptySquare;
10057       if((toX > fromX) != (piece == WhiteRook)) {
10058         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10059       } else {
10060         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10061       }
10062     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10063                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10064       board[EP_STATUS] = EP_NONE;
10065       board[fromY][fromX] = EmptySquare;
10066       board[toY][toX] = EmptySquare;
10067       if((toX > fromX) != (piece == BlackRook)) {
10068         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10069       } else {
10070         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10071       }
10072     /* End of code added by Tord */
10073
10074     } else if (board[fromY][fromX] == king
10075         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10076         && toY == fromY && toX > fromX+1) {
10077         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10078         board[fromY][toX-1] = board[fromY][rookX];
10079         board[fromY][rookX] = EmptySquare;
10080         board[fromY][fromX] = EmptySquare;
10081         board[toY][toX] = king;
10082     } else if (board[fromY][fromX] == king
10083         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10084                && toY == fromY && toX < fromX-1) {
10085         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10086         board[fromY][toX+1] = board[fromY][rookX];
10087         board[fromY][rookX] = EmptySquare;
10088         board[fromY][fromX] = EmptySquare;
10089         board[toY][toX] = king;
10090     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10091                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10092                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10093                ) {
10094         /* white pawn promotion */
10095         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10096         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10097             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10098         board[fromY][fromX] = EmptySquare;
10099     } else if ((fromY >= BOARD_HEIGHT>>1)
10100                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10101                && (toX != fromX)
10102                && gameInfo.variant != VariantXiangqi
10103                && gameInfo.variant != VariantBerolina
10104                && (pawn == WhitePawn)
10105                && (board[toY][toX] == EmptySquare)) {
10106         board[fromY][fromX] = EmptySquare;
10107         board[toY][toX] = piece;
10108         if(toY == epRank - 128 + 1)
10109             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10110         else
10111             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10112     } else if ((fromY == BOARD_HEIGHT-4)
10113                && (toX == fromX)
10114                && gameInfo.variant == VariantBerolina
10115                && (board[fromY][fromX] == WhitePawn)
10116                && (board[toY][toX] == EmptySquare)) {
10117         board[fromY][fromX] = EmptySquare;
10118         board[toY][toX] = WhitePawn;
10119         if(oldEP & EP_BEROLIN_A) {
10120                 captured = board[fromY][fromX-1];
10121                 board[fromY][fromX-1] = EmptySquare;
10122         }else{  captured = board[fromY][fromX+1];
10123                 board[fromY][fromX+1] = EmptySquare;
10124         }
10125     } else if (board[fromY][fromX] == king
10126         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10127                && toY == fromY && toX > fromX+1) {
10128         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10129         board[fromY][toX-1] = board[fromY][rookX];
10130         board[fromY][rookX] = EmptySquare;
10131         board[fromY][fromX] = EmptySquare;
10132         board[toY][toX] = king;
10133     } else if (board[fromY][fromX] == king
10134         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10135                && toY == fromY && toX < fromX-1) {
10136         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10137         board[fromY][toX+1] = board[fromY][rookX];
10138         board[fromY][rookX] = EmptySquare;
10139         board[fromY][fromX] = EmptySquare;
10140         board[toY][toX] = king;
10141     } else if (fromY == 7 && fromX == 3
10142                && board[fromY][fromX] == BlackKing
10143                && toY == 7 && toX == 5) {
10144         board[fromY][fromX] = EmptySquare;
10145         board[toY][toX] = BlackKing;
10146         board[fromY][7] = EmptySquare;
10147         board[toY][4] = BlackRook;
10148     } else if (fromY == 7 && fromX == 3
10149                && board[fromY][fromX] == BlackKing
10150                && toY == 7 && toX == 1) {
10151         board[fromY][fromX] = EmptySquare;
10152         board[toY][toX] = BlackKing;
10153         board[fromY][0] = EmptySquare;
10154         board[toY][2] = BlackRook;
10155     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10156                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10157                && toY < promoRank && promoChar
10158                ) {
10159         /* black pawn promotion */
10160         board[toY][toX] = CharToPiece(ToLower(promoChar));
10161         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10162             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10163         board[fromY][fromX] = EmptySquare;
10164     } else if ((fromY < BOARD_HEIGHT>>1)
10165                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10166                && (toX != fromX)
10167                && gameInfo.variant != VariantXiangqi
10168                && gameInfo.variant != VariantBerolina
10169                && (pawn == BlackPawn)
10170                && (board[toY][toX] == EmptySquare)) {
10171         board[fromY][fromX] = EmptySquare;
10172         board[toY][toX] = piece;
10173         if(toY == epRank - 128 - 1)
10174             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10175         else
10176             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10177     } else if ((fromY == 3)
10178                && (toX == fromX)
10179                && gameInfo.variant == VariantBerolina
10180                && (board[fromY][fromX] == BlackPawn)
10181                && (board[toY][toX] == EmptySquare)) {
10182         board[fromY][fromX] = EmptySquare;
10183         board[toY][toX] = BlackPawn;
10184         if(oldEP & EP_BEROLIN_A) {
10185                 captured = board[fromY][fromX-1];
10186                 board[fromY][fromX-1] = EmptySquare;
10187         }else{  captured = board[fromY][fromX+1];
10188                 board[fromY][fromX+1] = EmptySquare;
10189         }
10190     } else {
10191         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10192         board[fromY][fromX] = EmptySquare;
10193         board[toY][toX] = piece;
10194     }
10195   }
10196
10197     if (gameInfo.holdingsWidth != 0) {
10198
10199       /* !!A lot more code needs to be written to support holdings  */
10200       /* [HGM] OK, so I have written it. Holdings are stored in the */
10201       /* penultimate board files, so they are automaticlly stored   */
10202       /* in the game history.                                       */
10203       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10204                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10205         /* Delete from holdings, by decreasing count */
10206         /* and erasing image if necessary            */
10207         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10208         if(p < (int) BlackPawn) { /* white drop */
10209              p -= (int)WhitePawn;
10210                  p = PieceToNumber((ChessSquare)p);
10211              if(p >= gameInfo.holdingsSize) p = 0;
10212              if(--board[p][BOARD_WIDTH-2] <= 0)
10213                   board[p][BOARD_WIDTH-1] = EmptySquare;
10214              if((int)board[p][BOARD_WIDTH-2] < 0)
10215                         board[p][BOARD_WIDTH-2] = 0;
10216         } else {                  /* black drop */
10217              p -= (int)BlackPawn;
10218                  p = PieceToNumber((ChessSquare)p);
10219              if(p >= gameInfo.holdingsSize) p = 0;
10220              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10221                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10222              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10223                         board[BOARD_HEIGHT-1-p][1] = 0;
10224         }
10225       }
10226       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10227           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10228         /* [HGM] holdings: Add to holdings, if holdings exist */
10229         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10230                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10231                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10232         }
10233         p = (int) captured;
10234         if (p >= (int) BlackPawn) {
10235           p -= (int)BlackPawn;
10236           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10237                   /* Restore shogi-promoted piece to its original  first */
10238                   captured = (ChessSquare) (DEMOTED captured);
10239                   p = DEMOTED p;
10240           }
10241           p = PieceToNumber((ChessSquare)p);
10242           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10243           board[p][BOARD_WIDTH-2]++;
10244           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10245         } else {
10246           p -= (int)WhitePawn;
10247           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10248                   captured = (ChessSquare) (DEMOTED captured);
10249                   p = DEMOTED p;
10250           }
10251           p = PieceToNumber((ChessSquare)p);
10252           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10253           board[BOARD_HEIGHT-1-p][1]++;
10254           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10255         }
10256       }
10257     } else if (gameInfo.variant == VariantAtomic) {
10258       if (captured != EmptySquare) {
10259         int y, x;
10260         for (y = toY-1; y <= toY+1; y++) {
10261           for (x = toX-1; x <= toX+1; x++) {
10262             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10263                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10264               board[y][x] = EmptySquare;
10265             }
10266           }
10267         }
10268         board[toY][toX] = EmptySquare;
10269       }
10270     }
10271
10272     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10273         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10274     } else
10275     if(promoChar == '+') {
10276         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10277         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10278         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10279           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10280     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10281         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10282         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10283            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10284         board[toY][toX] = newPiece;
10285     }
10286     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10287                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10288         // [HGM] superchess: take promotion piece out of holdings
10289         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10290         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10291             if(!--board[k][BOARD_WIDTH-2])
10292                 board[k][BOARD_WIDTH-1] = EmptySquare;
10293         } else {
10294             if(!--board[BOARD_HEIGHT-1-k][1])
10295                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10296         }
10297     }
10298 }
10299
10300 /* Updates forwardMostMove */
10301 void
10302 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10303 {
10304     int x = toX, y = toY;
10305     char *s = parseList[forwardMostMove];
10306     ChessSquare p = boards[forwardMostMove][toY][toX];
10307 //    forwardMostMove++; // [HGM] bare: moved downstream
10308
10309     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10310     (void) CoordsToAlgebraic(boards[forwardMostMove],
10311                              PosFlags(forwardMostMove),
10312                              fromY, fromX, y, x, promoChar,
10313                              s);
10314     if(killX >= 0 && killY >= 0)
10315         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10316
10317     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10318         int timeLeft; static int lastLoadFlag=0; int king, piece;
10319         piece = boards[forwardMostMove][fromY][fromX];
10320         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10321         if(gameInfo.variant == VariantKnightmate)
10322             king += (int) WhiteUnicorn - (int) WhiteKing;
10323         if(forwardMostMove == 0) {
10324             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10325                 fprintf(serverMoves, "%s;", UserName());
10326             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10327                 fprintf(serverMoves, "%s;", second.tidy);
10328             fprintf(serverMoves, "%s;", first.tidy);
10329             if(gameMode == MachinePlaysWhite)
10330                 fprintf(serverMoves, "%s;", UserName());
10331             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10332                 fprintf(serverMoves, "%s;", second.tidy);
10333         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10334         lastLoadFlag = loadFlag;
10335         // print base move
10336         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10337         // print castling suffix
10338         if( toY == fromY && piece == king ) {
10339             if(toX-fromX > 1)
10340                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10341             if(fromX-toX >1)
10342                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10343         }
10344         // e.p. suffix
10345         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10346              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10347              boards[forwardMostMove][toY][toX] == EmptySquare
10348              && fromX != toX && fromY != toY)
10349                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10350         // promotion suffix
10351         if(promoChar != NULLCHAR) {
10352             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10353                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10354                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10355             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10356         }
10357         if(!loadFlag) {
10358                 char buf[MOVE_LEN*2], *p; int len;
10359             fprintf(serverMoves, "/%d/%d",
10360                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10361             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10362             else                      timeLeft = blackTimeRemaining/1000;
10363             fprintf(serverMoves, "/%d", timeLeft);
10364                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10365                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10366                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10367                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10368             fprintf(serverMoves, "/%s", buf);
10369         }
10370         fflush(serverMoves);
10371     }
10372
10373     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10374         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10375       return;
10376     }
10377     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10378     if (commentList[forwardMostMove+1] != NULL) {
10379         free(commentList[forwardMostMove+1]);
10380         commentList[forwardMostMove+1] = NULL;
10381     }
10382     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10383     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10384     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10385     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10386     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10387     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10388     adjustedClock = FALSE;
10389     gameInfo.result = GameUnfinished;
10390     if (gameInfo.resultDetails != NULL) {
10391         free(gameInfo.resultDetails);
10392         gameInfo.resultDetails = NULL;
10393     }
10394     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10395                               moveList[forwardMostMove - 1]);
10396     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10397       case MT_NONE:
10398       case MT_STALEMATE:
10399       default:
10400         break;
10401       case MT_CHECK:
10402         if(!IS_SHOGI(gameInfo.variant))
10403             strcat(parseList[forwardMostMove - 1], "+");
10404         break;
10405       case MT_CHECKMATE:
10406       case MT_STAINMATE:
10407         strcat(parseList[forwardMostMove - 1], "#");
10408         break;
10409     }
10410 }
10411
10412 /* Updates currentMove if not pausing */
10413 void
10414 ShowMove (int fromX, int fromY, int toX, int toY)
10415 {
10416     int instant = (gameMode == PlayFromGameFile) ?
10417         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10418     if(appData.noGUI) return;
10419     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10420         if (!instant) {
10421             if (forwardMostMove == currentMove + 1) {
10422                 AnimateMove(boards[forwardMostMove - 1],
10423                             fromX, fromY, toX, toY);
10424             }
10425         }
10426         currentMove = forwardMostMove;
10427     }
10428
10429     killX = killY = -1; // [HGM] lion: used up
10430
10431     if (instant) return;
10432
10433     DisplayMove(currentMove - 1);
10434     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10435             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10436                 SetHighlights(fromX, fromY, toX, toY);
10437             }
10438     }
10439     DrawPosition(FALSE, boards[currentMove]);
10440     DisplayBothClocks();
10441     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10442 }
10443
10444 void
10445 SendEgtPath (ChessProgramState *cps)
10446 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10447         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10448
10449         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10450
10451         while(*p) {
10452             char c, *q = name+1, *r, *s;
10453
10454             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10455             while(*p && *p != ',') *q++ = *p++;
10456             *q++ = ':'; *q = 0;
10457             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10458                 strcmp(name, ",nalimov:") == 0 ) {
10459                 // take nalimov path from the menu-changeable option first, if it is defined
10460               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10461                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10462             } else
10463             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10464                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10465                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10466                 s = r = StrStr(s, ":") + 1; // beginning of path info
10467                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10468                 c = *r; *r = 0;             // temporarily null-terminate path info
10469                     *--q = 0;               // strip of trailig ':' from name
10470                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10471                 *r = c;
10472                 SendToProgram(buf,cps);     // send egtbpath command for this format
10473             }
10474             if(*p == ',') p++; // read away comma to position for next format name
10475         }
10476 }
10477
10478 static int
10479 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10480 {
10481       int width = 8, height = 8, holdings = 0;             // most common sizes
10482       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10483       // correct the deviations default for each variant
10484       if( v == VariantXiangqi ) width = 9,  height = 10;
10485       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10486       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10487       if( v == VariantCapablanca || v == VariantCapaRandom ||
10488           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10489                                 width = 10;
10490       if( v == VariantCourier ) width = 12;
10491       if( v == VariantSuper )                            holdings = 8;
10492       if( v == VariantGreat )   width = 10,              holdings = 8;
10493       if( v == VariantSChess )                           holdings = 7;
10494       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10495       if( v == VariantChuChess) width = 10, height = 10;
10496       if( v == VariantChu )     width = 12, height = 12;
10497       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10498              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10499              holdingsSize >= 0 && holdingsSize != holdings;
10500 }
10501
10502 char variantError[MSG_SIZ];
10503
10504 char *
10505 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10506 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10507       char *p, *variant = VariantName(v);
10508       static char b[MSG_SIZ];
10509       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10510            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10511                                                holdingsSize, variant); // cook up sized variant name
10512            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10513            if(StrStr(list, b) == NULL) {
10514                // specific sized variant not known, check if general sizing allowed
10515                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10516                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10517                             boardWidth, boardHeight, holdingsSize, engine);
10518                    return NULL;
10519                }
10520                /* [HGM] here we really should compare with the maximum supported board size */
10521            }
10522       } else snprintf(b, MSG_SIZ,"%s", variant);
10523       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10524       p = StrStr(list, b);
10525       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10526       if(p == NULL) {
10527           // occurs not at all in list, or only as sub-string
10528           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10529           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10530               int l = strlen(variantError);
10531               char *q;
10532               while(p != list && p[-1] != ',') p--;
10533               q = strchr(p, ',');
10534               if(q) *q = NULLCHAR;
10535               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10536               if(q) *q= ',';
10537           }
10538           return NULL;
10539       }
10540       return b;
10541 }
10542
10543 void
10544 InitChessProgram (ChessProgramState *cps, int setup)
10545 /* setup needed to setup FRC opening position */
10546 {
10547     char buf[MSG_SIZ], *b;
10548     if (appData.noChessProgram) return;
10549     hintRequested = FALSE;
10550     bookRequested = FALSE;
10551
10552     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10553     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10554     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10555     if(cps->memSize) { /* [HGM] memory */
10556       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10557         SendToProgram(buf, cps);
10558     }
10559     SendEgtPath(cps); /* [HGM] EGT */
10560     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10561       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10562         SendToProgram(buf, cps);
10563     }
10564
10565     setboardSpoiledMachineBlack = FALSE;
10566     SendToProgram(cps->initString, cps);
10567     if (gameInfo.variant != VariantNormal &&
10568         gameInfo.variant != VariantLoadable
10569         /* [HGM] also send variant if board size non-standard */
10570         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10571
10572       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10573                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10574       if (b == NULL) {
10575         VariantClass v;
10576         char c, *q = cps->variants, *p = strchr(q, ',');
10577         if(p) *p = NULLCHAR;
10578         v = StringToVariant(q);
10579         DisplayError(variantError, 0);
10580         if(v != VariantUnknown && cps == &first) {
10581             int w, h, s;
10582             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10583                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10584             ASSIGN(appData.variant, q);
10585             Reset(TRUE, FALSE);
10586         }
10587         if(p) *p = ',';
10588         return;
10589       }
10590
10591       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10592       SendToProgram(buf, cps);
10593     }
10594     currentlyInitializedVariant = gameInfo.variant;
10595
10596     /* [HGM] send opening position in FRC to first engine */
10597     if(setup) {
10598           SendToProgram("force\n", cps);
10599           SendBoard(cps, 0);
10600           /* engine is now in force mode! Set flag to wake it up after first move. */
10601           setboardSpoiledMachineBlack = 1;
10602     }
10603
10604     if (cps->sendICS) {
10605       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10606       SendToProgram(buf, cps);
10607     }
10608     cps->maybeThinking = FALSE;
10609     cps->offeredDraw = 0;
10610     if (!appData.icsActive) {
10611         SendTimeControl(cps, movesPerSession, timeControl,
10612                         timeIncrement, appData.searchDepth,
10613                         searchTime);
10614     }
10615     if (appData.showThinking
10616         // [HGM] thinking: four options require thinking output to be sent
10617         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10618                                 ) {
10619         SendToProgram("post\n", cps);
10620     }
10621     SendToProgram("hard\n", cps);
10622     if (!appData.ponderNextMove) {
10623         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10624            it without being sure what state we are in first.  "hard"
10625            is not a toggle, so that one is OK.
10626          */
10627         SendToProgram("easy\n", cps);
10628     }
10629     if (cps->usePing) {
10630       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10631       SendToProgram(buf, cps);
10632     }
10633     cps->initDone = TRUE;
10634     ClearEngineOutputPane(cps == &second);
10635 }
10636
10637
10638 void
10639 ResendOptions (ChessProgramState *cps)
10640 { // send the stored value of the options
10641   int i;
10642   char buf[MSG_SIZ];
10643   Option *opt = cps->option;
10644   for(i=0; i<cps->nrOptions; i++, opt++) {
10645       switch(opt->type) {
10646         case Spin:
10647         case Slider:
10648         case CheckBox:
10649             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10650           break;
10651         case ComboBox:
10652           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10653           break;
10654         default:
10655             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10656           break;
10657         case Button:
10658         case SaveButton:
10659           continue;
10660       }
10661       SendToProgram(buf, cps);
10662   }
10663 }
10664
10665 void
10666 StartChessProgram (ChessProgramState *cps)
10667 {
10668     char buf[MSG_SIZ];
10669     int err;
10670
10671     if (appData.noChessProgram) return;
10672     cps->initDone = FALSE;
10673
10674     if (strcmp(cps->host, "localhost") == 0) {
10675         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10676     } else if (*appData.remoteShell == NULLCHAR) {
10677         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10678     } else {
10679         if (*appData.remoteUser == NULLCHAR) {
10680           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10681                     cps->program);
10682         } else {
10683           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10684                     cps->host, appData.remoteUser, cps->program);
10685         }
10686         err = StartChildProcess(buf, "", &cps->pr);
10687     }
10688
10689     if (err != 0) {
10690       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10691         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10692         if(cps != &first) return;
10693         appData.noChessProgram = TRUE;
10694         ThawUI();
10695         SetNCPMode();
10696 //      DisplayFatalError(buf, err, 1);
10697 //      cps->pr = NoProc;
10698 //      cps->isr = NULL;
10699         return;
10700     }
10701
10702     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10703     if (cps->protocolVersion > 1) {
10704       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10705       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10706         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10707         cps->comboCnt = 0;  //                and values of combo boxes
10708       }
10709       SendToProgram(buf, cps);
10710       if(cps->reload) ResendOptions(cps);
10711     } else {
10712       SendToProgram("xboard\n", cps);
10713     }
10714 }
10715
10716 void
10717 TwoMachinesEventIfReady P((void))
10718 {
10719   static int curMess = 0;
10720   if (first.lastPing != first.lastPong) {
10721     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10722     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10723     return;
10724   }
10725   if (second.lastPing != second.lastPong) {
10726     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10727     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10728     return;
10729   }
10730   DisplayMessage("", ""); curMess = 0;
10731   TwoMachinesEvent();
10732 }
10733
10734 char *
10735 MakeName (char *template)
10736 {
10737     time_t clock;
10738     struct tm *tm;
10739     static char buf[MSG_SIZ];
10740     char *p = buf;
10741     int i;
10742
10743     clock = time((time_t *)NULL);
10744     tm = localtime(&clock);
10745
10746     while(*p++ = *template++) if(p[-1] == '%') {
10747         switch(*template++) {
10748           case 0:   *p = 0; return buf;
10749           case 'Y': i = tm->tm_year+1900; break;
10750           case 'y': i = tm->tm_year-100; break;
10751           case 'M': i = tm->tm_mon+1; break;
10752           case 'd': i = tm->tm_mday; break;
10753           case 'h': i = tm->tm_hour; break;
10754           case 'm': i = tm->tm_min; break;
10755           case 's': i = tm->tm_sec; break;
10756           default:  i = 0;
10757         }
10758         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10759     }
10760     return buf;
10761 }
10762
10763 int
10764 CountPlayers (char *p)
10765 {
10766     int n = 0;
10767     while(p = strchr(p, '\n')) p++, n++; // count participants
10768     return n;
10769 }
10770
10771 FILE *
10772 WriteTourneyFile (char *results, FILE *f)
10773 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10774     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10775     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10776         // create a file with tournament description
10777         fprintf(f, "-participants {%s}\n", appData.participants);
10778         fprintf(f, "-seedBase %d\n", appData.seedBase);
10779         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10780         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10781         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10782         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10783         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10784         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10785         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10786         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10787         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10788         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10789         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10790         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10791         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10792         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10793         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10794         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10795         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10796         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10797         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10798         fprintf(f, "-smpCores %d\n", appData.smpCores);
10799         if(searchTime > 0)
10800                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10801         else {
10802                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10803                 fprintf(f, "-tc %s\n", appData.timeControl);
10804                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10805         }
10806         fprintf(f, "-results \"%s\"\n", results);
10807     }
10808     return f;
10809 }
10810
10811 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10812
10813 void
10814 Substitute (char *participants, int expunge)
10815 {
10816     int i, changed, changes=0, nPlayers=0;
10817     char *p, *q, *r, buf[MSG_SIZ];
10818     if(participants == NULL) return;
10819     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10820     r = p = participants; q = appData.participants;
10821     while(*p && *p == *q) {
10822         if(*p == '\n') r = p+1, nPlayers++;
10823         p++; q++;
10824     }
10825     if(*p) { // difference
10826         while(*p && *p++ != '\n');
10827         while(*q && *q++ != '\n');
10828       changed = nPlayers;
10829         changes = 1 + (strcmp(p, q) != 0);
10830     }
10831     if(changes == 1) { // a single engine mnemonic was changed
10832         q = r; while(*q) nPlayers += (*q++ == '\n');
10833         p = buf; while(*r && (*p = *r++) != '\n') p++;
10834         *p = NULLCHAR;
10835         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10836         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10837         if(mnemonic[i]) { // The substitute is valid
10838             FILE *f;
10839             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10840                 flock(fileno(f), LOCK_EX);
10841                 ParseArgsFromFile(f);
10842                 fseek(f, 0, SEEK_SET);
10843                 FREE(appData.participants); appData.participants = participants;
10844                 if(expunge) { // erase results of replaced engine
10845                     int len = strlen(appData.results), w, b, dummy;
10846                     for(i=0; i<len; i++) {
10847                         Pairing(i, nPlayers, &w, &b, &dummy);
10848                         if((w == changed || b == changed) && appData.results[i] == '*') {
10849                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10850                             fclose(f);
10851                             return;
10852                         }
10853                     }
10854                     for(i=0; i<len; i++) {
10855                         Pairing(i, nPlayers, &w, &b, &dummy);
10856                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10857                     }
10858                 }
10859                 WriteTourneyFile(appData.results, f);
10860                 fclose(f); // release lock
10861                 return;
10862             }
10863         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10864     }
10865     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10866     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10867     free(participants);
10868     return;
10869 }
10870
10871 int
10872 CheckPlayers (char *participants)
10873 {
10874         int i;
10875         char buf[MSG_SIZ], *p;
10876         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10877         while(p = strchr(participants, '\n')) {
10878             *p = NULLCHAR;
10879             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10880             if(!mnemonic[i]) {
10881                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10882                 *p = '\n';
10883                 DisplayError(buf, 0);
10884                 return 1;
10885             }
10886             *p = '\n';
10887             participants = p + 1;
10888         }
10889         return 0;
10890 }
10891
10892 int
10893 CreateTourney (char *name)
10894 {
10895         FILE *f;
10896         if(matchMode && strcmp(name, appData.tourneyFile)) {
10897              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10898         }
10899         if(name[0] == NULLCHAR) {
10900             if(appData.participants[0])
10901                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10902             return 0;
10903         }
10904         f = fopen(name, "r");
10905         if(f) { // file exists
10906             ASSIGN(appData.tourneyFile, name);
10907             ParseArgsFromFile(f); // parse it
10908         } else {
10909             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10910             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10911                 DisplayError(_("Not enough participants"), 0);
10912                 return 0;
10913             }
10914             if(CheckPlayers(appData.participants)) return 0;
10915             ASSIGN(appData.tourneyFile, name);
10916             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10917             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10918         }
10919         fclose(f);
10920         appData.noChessProgram = FALSE;
10921         appData.clockMode = TRUE;
10922         SetGNUMode();
10923         return 1;
10924 }
10925
10926 int
10927 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10928 {
10929     char buf[MSG_SIZ], *p, *q;
10930     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10931     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10932     skip = !all && group[0]; // if group requested, we start in skip mode
10933     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10934         p = names; q = buf; header = 0;
10935         while(*p && *p != '\n') *q++ = *p++;
10936         *q = 0;
10937         if(*p == '\n') p++;
10938         if(buf[0] == '#') {
10939             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10940             depth++; // we must be entering a new group
10941             if(all) continue; // suppress printing group headers when complete list requested
10942             header = 1;
10943             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10944         }
10945         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10946         if(engineList[i]) free(engineList[i]);
10947         engineList[i] = strdup(buf);
10948         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10949         if(engineMnemonic[i]) free(engineMnemonic[i]);
10950         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10951             strcat(buf, " (");
10952             sscanf(q + 8, "%s", buf + strlen(buf));
10953             strcat(buf, ")");
10954         }
10955         engineMnemonic[i] = strdup(buf);
10956         i++;
10957     }
10958     engineList[i] = engineMnemonic[i] = NULL;
10959     return i;
10960 }
10961
10962 // following implemented as macro to avoid type limitations
10963 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10964
10965 void
10966 SwapEngines (int n)
10967 {   // swap settings for first engine and other engine (so far only some selected options)
10968     int h;
10969     char *p;
10970     if(n == 0) return;
10971     SWAP(directory, p)
10972     SWAP(chessProgram, p)
10973     SWAP(isUCI, h)
10974     SWAP(hasOwnBookUCI, h)
10975     SWAP(protocolVersion, h)
10976     SWAP(reuse, h)
10977     SWAP(scoreIsAbsolute, h)
10978     SWAP(timeOdds, h)
10979     SWAP(logo, p)
10980     SWAP(pgnName, p)
10981     SWAP(pvSAN, h)
10982     SWAP(engOptions, p)
10983     SWAP(engInitString, p)
10984     SWAP(computerString, p)
10985     SWAP(features, p)
10986     SWAP(fenOverride, p)
10987     SWAP(NPS, h)
10988     SWAP(accumulateTC, h)
10989     SWAP(drawDepth, h)
10990     SWAP(host, p)
10991     SWAP(pseudo, h)
10992 }
10993
10994 int
10995 GetEngineLine (char *s, int n)
10996 {
10997     int i;
10998     char buf[MSG_SIZ];
10999     extern char *icsNames;
11000     if(!s || !*s) return 0;
11001     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11002     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11003     if(!mnemonic[i]) return 0;
11004     if(n == 11) return 1; // just testing if there was a match
11005     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11006     if(n == 1) SwapEngines(n);
11007     ParseArgsFromString(buf);
11008     if(n == 1) SwapEngines(n);
11009     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11010         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11011         ParseArgsFromString(buf);
11012     }
11013     return 1;
11014 }
11015
11016 int
11017 SetPlayer (int player, char *p)
11018 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11019     int i;
11020     char buf[MSG_SIZ], *engineName;
11021     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11022     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11023     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11024     if(mnemonic[i]) {
11025         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11026         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11027         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11028         ParseArgsFromString(buf);
11029     } else { // no engine with this nickname is installed!
11030         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11031         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11032         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11033         ModeHighlight();
11034         DisplayError(buf, 0);
11035         return 0;
11036     }
11037     free(engineName);
11038     return i;
11039 }
11040
11041 char *recentEngines;
11042
11043 void
11044 RecentEngineEvent (int nr)
11045 {
11046     int n;
11047 //    SwapEngines(1); // bump first to second
11048 //    ReplaceEngine(&second, 1); // and load it there
11049     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11050     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11051     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11052         ReplaceEngine(&first, 0);
11053         FloatToFront(&appData.recentEngineList, command[n]);
11054     }
11055 }
11056
11057 int
11058 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11059 {   // determine players from game number
11060     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11061
11062     if(appData.tourneyType == 0) {
11063         roundsPerCycle = (nPlayers - 1) | 1;
11064         pairingsPerRound = nPlayers / 2;
11065     } else if(appData.tourneyType > 0) {
11066         roundsPerCycle = nPlayers - appData.tourneyType;
11067         pairingsPerRound = appData.tourneyType;
11068     }
11069     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11070     gamesPerCycle = gamesPerRound * roundsPerCycle;
11071     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11072     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11073     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11074     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11075     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11076     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11077
11078     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11079     if(appData.roundSync) *syncInterval = gamesPerRound;
11080
11081     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11082
11083     if(appData.tourneyType == 0) {
11084         if(curPairing == (nPlayers-1)/2 ) {
11085             *whitePlayer = curRound;
11086             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11087         } else {
11088             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11089             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11090             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11091             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11092         }
11093     } else if(appData.tourneyType > 1) {
11094         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11095         *whitePlayer = curRound + appData.tourneyType;
11096     } else if(appData.tourneyType > 0) {
11097         *whitePlayer = curPairing;
11098         *blackPlayer = curRound + appData.tourneyType;
11099     }
11100
11101     // take care of white/black alternation per round.
11102     // For cycles and games this is already taken care of by default, derived from matchGame!
11103     return curRound & 1;
11104 }
11105
11106 int
11107 NextTourneyGame (int nr, int *swapColors)
11108 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11109     char *p, *q;
11110     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11111     FILE *tf;
11112     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11113     tf = fopen(appData.tourneyFile, "r");
11114     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11115     ParseArgsFromFile(tf); fclose(tf);
11116     InitTimeControls(); // TC might be altered from tourney file
11117
11118     nPlayers = CountPlayers(appData.participants); // count participants
11119     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11120     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11121
11122     if(syncInterval) {
11123         p = q = appData.results;
11124         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11125         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11126             DisplayMessage(_("Waiting for other game(s)"),"");
11127             waitingForGame = TRUE;
11128             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11129             return 0;
11130         }
11131         waitingForGame = FALSE;
11132     }
11133
11134     if(appData.tourneyType < 0) {
11135         if(nr>=0 && !pairingReceived) {
11136             char buf[1<<16];
11137             if(pairing.pr == NoProc) {
11138                 if(!appData.pairingEngine[0]) {
11139                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11140                     return 0;
11141                 }
11142                 StartChessProgram(&pairing); // starts the pairing engine
11143             }
11144             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11145             SendToProgram(buf, &pairing);
11146             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11147             SendToProgram(buf, &pairing);
11148             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11149         }
11150         pairingReceived = 0;                              // ... so we continue here
11151         *swapColors = 0;
11152         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11153         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11154         matchGame = 1; roundNr = nr / syncInterval + 1;
11155     }
11156
11157     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11158
11159     // redefine engines, engine dir, etc.
11160     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11161     if(first.pr == NoProc) {
11162       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11163       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11164     }
11165     if(second.pr == NoProc) {
11166       SwapEngines(1);
11167       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11168       SwapEngines(1);         // and make that valid for second engine by swapping
11169       InitEngine(&second, 1);
11170     }
11171     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11172     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11173     return OK;
11174 }
11175
11176 void
11177 NextMatchGame ()
11178 {   // performs game initialization that does not invoke engines, and then tries to start the game
11179     int res, firstWhite, swapColors = 0;
11180     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11181     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
11182         char buf[MSG_SIZ];
11183         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11184         if(strcmp(buf, currentDebugFile)) { // name has changed
11185             FILE *f = fopen(buf, "w");
11186             if(f) { // if opening the new file failed, just keep using the old one
11187                 ASSIGN(currentDebugFile, buf);
11188                 fclose(debugFP);
11189                 debugFP = f;
11190             }
11191             if(appData.serverFileName) {
11192                 if(serverFP) fclose(serverFP);
11193                 serverFP = fopen(appData.serverFileName, "w");
11194                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11195                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11196             }
11197         }
11198     }
11199     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11200     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11201     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11202     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11203     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11204     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11205     Reset(FALSE, first.pr != NoProc);
11206     res = LoadGameOrPosition(matchGame); // setup game
11207     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11208     if(!res) return; // abort when bad game/pos file
11209     TwoMachinesEvent();
11210 }
11211
11212 void
11213 UserAdjudicationEvent (int result)
11214 {
11215     ChessMove gameResult = GameIsDrawn;
11216
11217     if( result > 0 ) {
11218         gameResult = WhiteWins;
11219     }
11220     else if( result < 0 ) {
11221         gameResult = BlackWins;
11222     }
11223
11224     if( gameMode == TwoMachinesPlay ) {
11225         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11226     }
11227 }
11228
11229
11230 // [HGM] save: calculate checksum of game to make games easily identifiable
11231 int
11232 StringCheckSum (char *s)
11233 {
11234         int i = 0;
11235         if(s==NULL) return 0;
11236         while(*s) i = i*259 + *s++;
11237         return i;
11238 }
11239
11240 int
11241 GameCheckSum ()
11242 {
11243         int i, sum=0;
11244         for(i=backwardMostMove; i<forwardMostMove; i++) {
11245                 sum += pvInfoList[i].depth;
11246                 sum += StringCheckSum(parseList[i]);
11247                 sum += StringCheckSum(commentList[i]);
11248                 sum *= 261;
11249         }
11250         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11251         return sum + StringCheckSum(commentList[i]);
11252 } // end of save patch
11253
11254 void
11255 GameEnds (ChessMove result, char *resultDetails, int whosays)
11256 {
11257     GameMode nextGameMode;
11258     int isIcsGame;
11259     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11260
11261     if(endingGame) return; /* [HGM] crash: forbid recursion */
11262     endingGame = 1;
11263     if(twoBoards) { // [HGM] dual: switch back to one board
11264         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11265         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11266     }
11267     if (appData.debugMode) {
11268       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11269               result, resultDetails ? resultDetails : "(null)", whosays);
11270     }
11271
11272     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11273
11274     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11275
11276     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11277         /* If we are playing on ICS, the server decides when the
11278            game is over, but the engine can offer to draw, claim
11279            a draw, or resign.
11280          */
11281 #if ZIPPY
11282         if (appData.zippyPlay && first.initDone) {
11283             if (result == GameIsDrawn) {
11284                 /* In case draw still needs to be claimed */
11285                 SendToICS(ics_prefix);
11286                 SendToICS("draw\n");
11287             } else if (StrCaseStr(resultDetails, "resign")) {
11288                 SendToICS(ics_prefix);
11289                 SendToICS("resign\n");
11290             }
11291         }
11292 #endif
11293         endingGame = 0; /* [HGM] crash */
11294         return;
11295     }
11296
11297     /* If we're loading the game from a file, stop */
11298     if (whosays == GE_FILE) {
11299       (void) StopLoadGameTimer();
11300       gameFileFP = NULL;
11301     }
11302
11303     /* Cancel draw offers */
11304     first.offeredDraw = second.offeredDraw = 0;
11305
11306     /* If this is an ICS game, only ICS can really say it's done;
11307        if not, anyone can. */
11308     isIcsGame = (gameMode == IcsPlayingWhite ||
11309                  gameMode == IcsPlayingBlack ||
11310                  gameMode == IcsObserving    ||
11311                  gameMode == IcsExamining);
11312
11313     if (!isIcsGame || whosays == GE_ICS) {
11314         /* OK -- not an ICS game, or ICS said it was done */
11315         StopClocks();
11316         if (!isIcsGame && !appData.noChessProgram)
11317           SetUserThinkingEnables();
11318
11319         /* [HGM] if a machine claims the game end we verify this claim */
11320         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11321             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11322                 char claimer;
11323                 ChessMove trueResult = (ChessMove) -1;
11324
11325                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11326                                             first.twoMachinesColor[0] :
11327                                             second.twoMachinesColor[0] ;
11328
11329                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11330                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11331                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11332                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11333                 } else
11334                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11335                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11336                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11337                 } else
11338                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11339                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11340                 }
11341
11342                 // now verify win claims, but not in drop games, as we don't understand those yet
11343                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11344                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11345                     (result == WhiteWins && claimer == 'w' ||
11346                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11347                       if (appData.debugMode) {
11348                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11349                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11350                       }
11351                       if(result != trueResult) {
11352                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11353                               result = claimer == 'w' ? BlackWins : WhiteWins;
11354                               resultDetails = buf;
11355                       }
11356                 } else
11357                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11358                     && (forwardMostMove <= backwardMostMove ||
11359                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11360                         (claimer=='b')==(forwardMostMove&1))
11361                                                                                   ) {
11362                       /* [HGM] verify: draws that were not flagged are false claims */
11363                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11364                       result = claimer == 'w' ? BlackWins : WhiteWins;
11365                       resultDetails = buf;
11366                 }
11367                 /* (Claiming a loss is accepted no questions asked!) */
11368             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11369                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11370                 result = GameUnfinished;
11371                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11372             }
11373             /* [HGM] bare: don't allow bare King to win */
11374             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11375                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11376                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11377                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11378                && result != GameIsDrawn)
11379             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11380                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11381                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11382                         if(p >= 0 && p <= (int)WhiteKing) k++;
11383                 }
11384                 if (appData.debugMode) {
11385                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11386                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11387                 }
11388                 if(k <= 1) {
11389                         result = GameIsDrawn;
11390                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11391                         resultDetails = buf;
11392                 }
11393             }
11394         }
11395
11396
11397         if(serverMoves != NULL && !loadFlag) { char c = '=';
11398             if(result==WhiteWins) c = '+';
11399             if(result==BlackWins) c = '-';
11400             if(resultDetails != NULL)
11401                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11402         }
11403         if (resultDetails != NULL) {
11404             gameInfo.result = result;
11405             gameInfo.resultDetails = StrSave(resultDetails);
11406
11407             /* display last move only if game was not loaded from file */
11408             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11409                 DisplayMove(currentMove - 1);
11410
11411             if (forwardMostMove != 0) {
11412                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11413                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11414                                                                 ) {
11415                     if (*appData.saveGameFile != NULLCHAR) {
11416                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11417                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11418                         else
11419                         SaveGameToFile(appData.saveGameFile, TRUE);
11420                     } else if (appData.autoSaveGames) {
11421                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11422                     }
11423                     if (*appData.savePositionFile != NULLCHAR) {
11424                         SavePositionToFile(appData.savePositionFile);
11425                     }
11426                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11427                 }
11428             }
11429
11430             /* Tell program how game ended in case it is learning */
11431             /* [HGM] Moved this to after saving the PGN, just in case */
11432             /* engine died and we got here through time loss. In that */
11433             /* case we will get a fatal error writing the pipe, which */
11434             /* would otherwise lose us the PGN.                       */
11435             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11436             /* output during GameEnds should never be fatal anymore   */
11437             if (gameMode == MachinePlaysWhite ||
11438                 gameMode == MachinePlaysBlack ||
11439                 gameMode == TwoMachinesPlay ||
11440                 gameMode == IcsPlayingWhite ||
11441                 gameMode == IcsPlayingBlack ||
11442                 gameMode == BeginningOfGame) {
11443                 char buf[MSG_SIZ];
11444                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11445                         resultDetails);
11446                 if (first.pr != NoProc) {
11447                     SendToProgram(buf, &first);
11448                 }
11449                 if (second.pr != NoProc &&
11450                     gameMode == TwoMachinesPlay) {
11451                     SendToProgram(buf, &second);
11452                 }
11453             }
11454         }
11455
11456         if (appData.icsActive) {
11457             if (appData.quietPlay &&
11458                 (gameMode == IcsPlayingWhite ||
11459                  gameMode == IcsPlayingBlack)) {
11460                 SendToICS(ics_prefix);
11461                 SendToICS("set shout 1\n");
11462             }
11463             nextGameMode = IcsIdle;
11464             ics_user_moved = FALSE;
11465             /* clean up premove.  It's ugly when the game has ended and the
11466              * premove highlights are still on the board.
11467              */
11468             if (gotPremove) {
11469               gotPremove = FALSE;
11470               ClearPremoveHighlights();
11471               DrawPosition(FALSE, boards[currentMove]);
11472             }
11473             if (whosays == GE_ICS) {
11474                 switch (result) {
11475                 case WhiteWins:
11476                     if (gameMode == IcsPlayingWhite)
11477                         PlayIcsWinSound();
11478                     else if(gameMode == IcsPlayingBlack)
11479                         PlayIcsLossSound();
11480                     break;
11481                 case BlackWins:
11482                     if (gameMode == IcsPlayingBlack)
11483                         PlayIcsWinSound();
11484                     else if(gameMode == IcsPlayingWhite)
11485                         PlayIcsLossSound();
11486                     break;
11487                 case GameIsDrawn:
11488                     PlayIcsDrawSound();
11489                     break;
11490                 default:
11491                     PlayIcsUnfinishedSound();
11492                 }
11493             }
11494             if(appData.quitNext) { ExitEvent(0); return; }
11495         } else if (gameMode == EditGame ||
11496                    gameMode == PlayFromGameFile ||
11497                    gameMode == AnalyzeMode ||
11498                    gameMode == AnalyzeFile) {
11499             nextGameMode = gameMode;
11500         } else {
11501             nextGameMode = EndOfGame;
11502         }
11503         pausing = FALSE;
11504         ModeHighlight();
11505     } else {
11506         nextGameMode = gameMode;
11507     }
11508
11509     if (appData.noChessProgram) {
11510         gameMode = nextGameMode;
11511         ModeHighlight();
11512         endingGame = 0; /* [HGM] crash */
11513         return;
11514     }
11515
11516     if (first.reuse) {
11517         /* Put first chess program into idle state */
11518         if (first.pr != NoProc &&
11519             (gameMode == MachinePlaysWhite ||
11520              gameMode == MachinePlaysBlack ||
11521              gameMode == TwoMachinesPlay ||
11522              gameMode == IcsPlayingWhite ||
11523              gameMode == IcsPlayingBlack ||
11524              gameMode == BeginningOfGame)) {
11525             SendToProgram("force\n", &first);
11526             if (first.usePing) {
11527               char buf[MSG_SIZ];
11528               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11529               SendToProgram(buf, &first);
11530             }
11531         }
11532     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11533         /* Kill off first chess program */
11534         if (first.isr != NULL)
11535           RemoveInputSource(first.isr);
11536         first.isr = NULL;
11537
11538         if (first.pr != NoProc) {
11539             ExitAnalyzeMode();
11540             DoSleep( appData.delayBeforeQuit );
11541             SendToProgram("quit\n", &first);
11542             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11543             first.reload = TRUE;
11544         }
11545         first.pr = NoProc;
11546     }
11547     if (second.reuse) {
11548         /* Put second chess program into idle state */
11549         if (second.pr != NoProc &&
11550             gameMode == TwoMachinesPlay) {
11551             SendToProgram("force\n", &second);
11552             if (second.usePing) {
11553               char buf[MSG_SIZ];
11554               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11555               SendToProgram(buf, &second);
11556             }
11557         }
11558     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11559         /* Kill off second chess program */
11560         if (second.isr != NULL)
11561           RemoveInputSource(second.isr);
11562         second.isr = NULL;
11563
11564         if (second.pr != NoProc) {
11565             DoSleep( appData.delayBeforeQuit );
11566             SendToProgram("quit\n", &second);
11567             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11568             second.reload = TRUE;
11569         }
11570         second.pr = NoProc;
11571     }
11572
11573     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11574         char resChar = '=';
11575         switch (result) {
11576         case WhiteWins:
11577           resChar = '+';
11578           if (first.twoMachinesColor[0] == 'w') {
11579             first.matchWins++;
11580           } else {
11581             second.matchWins++;
11582           }
11583           break;
11584         case BlackWins:
11585           resChar = '-';
11586           if (first.twoMachinesColor[0] == 'b') {
11587             first.matchWins++;
11588           } else {
11589             second.matchWins++;
11590           }
11591           break;
11592         case GameUnfinished:
11593           resChar = ' ';
11594         default:
11595           break;
11596         }
11597
11598         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11599         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11600             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11601             ReserveGame(nextGame, resChar); // sets nextGame
11602             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11603             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11604         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11605
11606         if (nextGame <= appData.matchGames && !abortMatch) {
11607             gameMode = nextGameMode;
11608             matchGame = nextGame; // this will be overruled in tourney mode!
11609             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11610             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11611             endingGame = 0; /* [HGM] crash */
11612             return;
11613         } else {
11614             gameMode = nextGameMode;
11615             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11616                      first.tidy, second.tidy,
11617                      first.matchWins, second.matchWins,
11618                      appData.matchGames - (first.matchWins + second.matchWins));
11619             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11620             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11621             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11622             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11623                 first.twoMachinesColor = "black\n";
11624                 second.twoMachinesColor = "white\n";
11625             } else {
11626                 first.twoMachinesColor = "white\n";
11627                 second.twoMachinesColor = "black\n";
11628             }
11629         }
11630     }
11631     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11632         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11633       ExitAnalyzeMode();
11634     gameMode = nextGameMode;
11635     ModeHighlight();
11636     endingGame = 0;  /* [HGM] crash */
11637     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11638         if(matchMode == TRUE) { // match through command line: exit with or without popup
11639             if(ranking) {
11640                 ToNrEvent(forwardMostMove);
11641                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11642                 else ExitEvent(0);
11643             } else DisplayFatalError(buf, 0, 0);
11644         } else { // match through menu; just stop, with or without popup
11645             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11646             ModeHighlight();
11647             if(ranking){
11648                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11649             } else DisplayNote(buf);
11650       }
11651       if(ranking) free(ranking);
11652     }
11653 }
11654
11655 /* Assumes program was just initialized (initString sent).
11656    Leaves program in force mode. */
11657 void
11658 FeedMovesToProgram (ChessProgramState *cps, int upto)
11659 {
11660     int i;
11661
11662     if (appData.debugMode)
11663       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11664               startedFromSetupPosition ? "position and " : "",
11665               backwardMostMove, upto, cps->which);
11666     if(currentlyInitializedVariant != gameInfo.variant) {
11667       char buf[MSG_SIZ];
11668         // [HGM] variantswitch: make engine aware of new variant
11669         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11670                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11671                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11672         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11673         SendToProgram(buf, cps);
11674         currentlyInitializedVariant = gameInfo.variant;
11675     }
11676     SendToProgram("force\n", cps);
11677     if (startedFromSetupPosition) {
11678         SendBoard(cps, backwardMostMove);
11679     if (appData.debugMode) {
11680         fprintf(debugFP, "feedMoves\n");
11681     }
11682     }
11683     for (i = backwardMostMove; i < upto; i++) {
11684         SendMoveToProgram(i, cps);
11685     }
11686 }
11687
11688
11689 int
11690 ResurrectChessProgram ()
11691 {
11692      /* The chess program may have exited.
11693         If so, restart it and feed it all the moves made so far. */
11694     static int doInit = 0;
11695
11696     if (appData.noChessProgram) return 1;
11697
11698     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11699         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11700         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11701         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11702     } else {
11703         if (first.pr != NoProc) return 1;
11704         StartChessProgram(&first);
11705     }
11706     InitChessProgram(&first, FALSE);
11707     FeedMovesToProgram(&first, currentMove);
11708
11709     if (!first.sendTime) {
11710         /* can't tell gnuchess what its clock should read,
11711            so we bow to its notion. */
11712         ResetClocks();
11713         timeRemaining[0][currentMove] = whiteTimeRemaining;
11714         timeRemaining[1][currentMove] = blackTimeRemaining;
11715     }
11716
11717     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11718                 appData.icsEngineAnalyze) && first.analysisSupport) {
11719       SendToProgram("analyze\n", &first);
11720       first.analyzing = TRUE;
11721     }
11722     return 1;
11723 }
11724
11725 /*
11726  * Button procedures
11727  */
11728 void
11729 Reset (int redraw, int init)
11730 {
11731     int i;
11732
11733     if (appData.debugMode) {
11734         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11735                 redraw, init, gameMode);
11736     }
11737     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11738     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11739     CleanupTail(); // [HGM] vari: delete any stored variations
11740     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11741     pausing = pauseExamInvalid = FALSE;
11742     startedFromSetupPosition = blackPlaysFirst = FALSE;
11743     firstMove = TRUE;
11744     whiteFlag = blackFlag = FALSE;
11745     userOfferedDraw = FALSE;
11746     hintRequested = bookRequested = FALSE;
11747     first.maybeThinking = FALSE;
11748     second.maybeThinking = FALSE;
11749     first.bookSuspend = FALSE; // [HGM] book
11750     second.bookSuspend = FALSE;
11751     thinkOutput[0] = NULLCHAR;
11752     lastHint[0] = NULLCHAR;
11753     ClearGameInfo(&gameInfo);
11754     gameInfo.variant = StringToVariant(appData.variant);
11755     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11756     ics_user_moved = ics_clock_paused = FALSE;
11757     ics_getting_history = H_FALSE;
11758     ics_gamenum = -1;
11759     white_holding[0] = black_holding[0] = NULLCHAR;
11760     ClearProgramStats();
11761     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11762
11763     ResetFrontEnd();
11764     ClearHighlights();
11765     flipView = appData.flipView;
11766     ClearPremoveHighlights();
11767     gotPremove = FALSE;
11768     alarmSounded = FALSE;
11769     killX = killY = -1; // [HGM] lion
11770
11771     GameEnds(EndOfFile, NULL, GE_PLAYER);
11772     if(appData.serverMovesName != NULL) {
11773         /* [HGM] prepare to make moves file for broadcasting */
11774         clock_t t = clock();
11775         if(serverMoves != NULL) fclose(serverMoves);
11776         serverMoves = fopen(appData.serverMovesName, "r");
11777         if(serverMoves != NULL) {
11778             fclose(serverMoves);
11779             /* delay 15 sec before overwriting, so all clients can see end */
11780             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11781         }
11782         serverMoves = fopen(appData.serverMovesName, "w");
11783     }
11784
11785     ExitAnalyzeMode();
11786     gameMode = BeginningOfGame;
11787     ModeHighlight();
11788     if(appData.icsActive) gameInfo.variant = VariantNormal;
11789     currentMove = forwardMostMove = backwardMostMove = 0;
11790     MarkTargetSquares(1);
11791     InitPosition(redraw);
11792     for (i = 0; i < MAX_MOVES; i++) {
11793         if (commentList[i] != NULL) {
11794             free(commentList[i]);
11795             commentList[i] = NULL;
11796         }
11797     }
11798     ResetClocks();
11799     timeRemaining[0][0] = whiteTimeRemaining;
11800     timeRemaining[1][0] = blackTimeRemaining;
11801
11802     if (first.pr == NoProc) {
11803         StartChessProgram(&first);
11804     }
11805     if (init) {
11806             InitChessProgram(&first, startedFromSetupPosition);
11807     }
11808     DisplayTitle("");
11809     DisplayMessage("", "");
11810     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11811     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11812     ClearMap();        // [HGM] exclude: invalidate map
11813 }
11814
11815 void
11816 AutoPlayGameLoop ()
11817 {
11818     for (;;) {
11819         if (!AutoPlayOneMove())
11820           return;
11821         if (matchMode || appData.timeDelay == 0)
11822           continue;
11823         if (appData.timeDelay < 0)
11824           return;
11825         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11826         break;
11827     }
11828 }
11829
11830 void
11831 AnalyzeNextGame()
11832 {
11833     ReloadGame(1); // next game
11834 }
11835
11836 int
11837 AutoPlayOneMove ()
11838 {
11839     int fromX, fromY, toX, toY;
11840
11841     if (appData.debugMode) {
11842       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11843     }
11844
11845     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11846       return FALSE;
11847
11848     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11849       pvInfoList[currentMove].depth = programStats.depth;
11850       pvInfoList[currentMove].score = programStats.score;
11851       pvInfoList[currentMove].time  = 0;
11852       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11853       else { // append analysis of final position as comment
11854         char buf[MSG_SIZ];
11855         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11856         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11857       }
11858       programStats.depth = 0;
11859     }
11860
11861     if (currentMove >= forwardMostMove) {
11862       if(gameMode == AnalyzeFile) {
11863           if(appData.loadGameIndex == -1) {
11864             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11865           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11866           } else {
11867           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11868         }
11869       }
11870 //      gameMode = EndOfGame;
11871 //      ModeHighlight();
11872
11873       /* [AS] Clear current move marker at the end of a game */
11874       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11875
11876       return FALSE;
11877     }
11878
11879     toX = moveList[currentMove][2] - AAA;
11880     toY = moveList[currentMove][3] - ONE;
11881
11882     if (moveList[currentMove][1] == '@') {
11883         if (appData.highlightLastMove) {
11884             SetHighlights(-1, -1, toX, toY);
11885         }
11886     } else {
11887         int viaX = moveList[currentMove][5] - AAA;
11888         int viaY = moveList[currentMove][6] - ONE;
11889         fromX = moveList[currentMove][0] - AAA;
11890         fromY = moveList[currentMove][1] - ONE;
11891
11892         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11893
11894         if(moveList[currentMove][4] == ';') { // multi-leg
11895             ChessSquare piece = boards[currentMove][viaY][viaX];
11896             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11897             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11898             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11899             boards[currentMove][viaY][viaX] = piece;
11900         } else
11901         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11902
11903         if (appData.highlightLastMove) {
11904             SetHighlights(fromX, fromY, toX, toY);
11905         }
11906     }
11907     DisplayMove(currentMove);
11908     SendMoveToProgram(currentMove++, &first);
11909     DisplayBothClocks();
11910     DrawPosition(FALSE, boards[currentMove]);
11911     // [HGM] PV info: always display, routine tests if empty
11912     DisplayComment(currentMove - 1, commentList[currentMove]);
11913     return TRUE;
11914 }
11915
11916
11917 int
11918 LoadGameOneMove (ChessMove readAhead)
11919 {
11920     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11921     char promoChar = NULLCHAR;
11922     ChessMove moveType;
11923     char move[MSG_SIZ];
11924     char *p, *q;
11925
11926     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11927         gameMode != AnalyzeMode && gameMode != Training) {
11928         gameFileFP = NULL;
11929         return FALSE;
11930     }
11931
11932     yyboardindex = forwardMostMove;
11933     if (readAhead != EndOfFile) {
11934       moveType = readAhead;
11935     } else {
11936       if (gameFileFP == NULL)
11937           return FALSE;
11938       moveType = (ChessMove) Myylex();
11939     }
11940
11941     done = FALSE;
11942     switch (moveType) {
11943       case Comment:
11944         if (appData.debugMode)
11945           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11946         p = yy_text;
11947
11948         /* append the comment but don't display it */
11949         AppendComment(currentMove, p, FALSE);
11950         return TRUE;
11951
11952       case WhiteCapturesEnPassant:
11953       case BlackCapturesEnPassant:
11954       case WhitePromotion:
11955       case BlackPromotion:
11956       case WhiteNonPromotion:
11957       case BlackNonPromotion:
11958       case NormalMove:
11959       case FirstLeg:
11960       case WhiteKingSideCastle:
11961       case WhiteQueenSideCastle:
11962       case BlackKingSideCastle:
11963       case BlackQueenSideCastle:
11964       case WhiteKingSideCastleWild:
11965       case WhiteQueenSideCastleWild:
11966       case BlackKingSideCastleWild:
11967       case BlackQueenSideCastleWild:
11968       /* PUSH Fabien */
11969       case WhiteHSideCastleFR:
11970       case WhiteASideCastleFR:
11971       case BlackHSideCastleFR:
11972       case BlackASideCastleFR:
11973       /* POP Fabien */
11974         if (appData.debugMode)
11975           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11976         fromX = currentMoveString[0] - AAA;
11977         fromY = currentMoveString[1] - ONE;
11978         toX = currentMoveString[2] - AAA;
11979         toY = currentMoveString[3] - ONE;
11980         promoChar = currentMoveString[4];
11981         if(promoChar == ';') promoChar = NULLCHAR;
11982         break;
11983
11984       case WhiteDrop:
11985       case BlackDrop:
11986         if (appData.debugMode)
11987           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11988         fromX = moveType == WhiteDrop ?
11989           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11990         (int) CharToPiece(ToLower(currentMoveString[0]));
11991         fromY = DROP_RANK;
11992         toX = currentMoveString[2] - AAA;
11993         toY = currentMoveString[3] - ONE;
11994         break;
11995
11996       case WhiteWins:
11997       case BlackWins:
11998       case GameIsDrawn:
11999       case GameUnfinished:
12000         if (appData.debugMode)
12001           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12002         p = strchr(yy_text, '{');
12003         if (p == NULL) p = strchr(yy_text, '(');
12004         if (p == NULL) {
12005             p = yy_text;
12006             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12007         } else {
12008             q = strchr(p, *p == '{' ? '}' : ')');
12009             if (q != NULL) *q = NULLCHAR;
12010             p++;
12011         }
12012         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12013         GameEnds(moveType, p, GE_FILE);
12014         done = TRUE;
12015         if (cmailMsgLoaded) {
12016             ClearHighlights();
12017             flipView = WhiteOnMove(currentMove);
12018             if (moveType == GameUnfinished) flipView = !flipView;
12019             if (appData.debugMode)
12020               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12021         }
12022         break;
12023
12024       case EndOfFile:
12025         if (appData.debugMode)
12026           fprintf(debugFP, "Parser hit end of file\n");
12027         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12028           case MT_NONE:
12029           case MT_CHECK:
12030             break;
12031           case MT_CHECKMATE:
12032           case MT_STAINMATE:
12033             if (WhiteOnMove(currentMove)) {
12034                 GameEnds(BlackWins, "Black mates", GE_FILE);
12035             } else {
12036                 GameEnds(WhiteWins, "White mates", GE_FILE);
12037             }
12038             break;
12039           case MT_STALEMATE:
12040             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12041             break;
12042         }
12043         done = TRUE;
12044         break;
12045
12046       case MoveNumberOne:
12047         if (lastLoadGameStart == GNUChessGame) {
12048             /* GNUChessGames have numbers, but they aren't move numbers */
12049             if (appData.debugMode)
12050               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12051                       yy_text, (int) moveType);
12052             return LoadGameOneMove(EndOfFile); /* tail recursion */
12053         }
12054         /* else fall thru */
12055
12056       case XBoardGame:
12057       case GNUChessGame:
12058       case PGNTag:
12059         /* Reached start of next game in file */
12060         if (appData.debugMode)
12061           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12062         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12063           case MT_NONE:
12064           case MT_CHECK:
12065             break;
12066           case MT_CHECKMATE:
12067           case MT_STAINMATE:
12068             if (WhiteOnMove(currentMove)) {
12069                 GameEnds(BlackWins, "Black mates", GE_FILE);
12070             } else {
12071                 GameEnds(WhiteWins, "White mates", GE_FILE);
12072             }
12073             break;
12074           case MT_STALEMATE:
12075             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12076             break;
12077         }
12078         done = TRUE;
12079         break;
12080
12081       case PositionDiagram:     /* should not happen; ignore */
12082       case ElapsedTime:         /* ignore */
12083       case NAG:                 /* ignore */
12084         if (appData.debugMode)
12085           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12086                   yy_text, (int) moveType);
12087         return LoadGameOneMove(EndOfFile); /* tail recursion */
12088
12089       case IllegalMove:
12090         if (appData.testLegality) {
12091             if (appData.debugMode)
12092               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12093             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12094                     (forwardMostMove / 2) + 1,
12095                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12096             DisplayError(move, 0);
12097             done = TRUE;
12098         } else {
12099             if (appData.debugMode)
12100               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12101                       yy_text, currentMoveString);
12102             if(currentMoveString[1] == '@') {
12103                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12104                 fromY = DROP_RANK;
12105             } else {
12106                 fromX = currentMoveString[0] - AAA;
12107                 fromY = currentMoveString[1] - ONE;
12108             }
12109             toX = currentMoveString[2] - AAA;
12110             toY = currentMoveString[3] - ONE;
12111             promoChar = currentMoveString[4];
12112         }
12113         break;
12114
12115       case AmbiguousMove:
12116         if (appData.debugMode)
12117           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12118         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12119                 (forwardMostMove / 2) + 1,
12120                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12121         DisplayError(move, 0);
12122         done = TRUE;
12123         break;
12124
12125       default:
12126       case ImpossibleMove:
12127         if (appData.debugMode)
12128           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12129         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12130                 (forwardMostMove / 2) + 1,
12131                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12132         DisplayError(move, 0);
12133         done = TRUE;
12134         break;
12135     }
12136
12137     if (done) {
12138         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12139             DrawPosition(FALSE, boards[currentMove]);
12140             DisplayBothClocks();
12141             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12142               DisplayComment(currentMove - 1, commentList[currentMove]);
12143         }
12144         (void) StopLoadGameTimer();
12145         gameFileFP = NULL;
12146         cmailOldMove = forwardMostMove;
12147         return FALSE;
12148     } else {
12149         /* currentMoveString is set as a side-effect of yylex */
12150
12151         thinkOutput[0] = NULLCHAR;
12152         MakeMove(fromX, fromY, toX, toY, promoChar);
12153         killX = killY = -1; // [HGM] lion: used up
12154         currentMove = forwardMostMove;
12155         return TRUE;
12156     }
12157 }
12158
12159 /* Load the nth game from the given file */
12160 int
12161 LoadGameFromFile (char *filename, int n, char *title, int useList)
12162 {
12163     FILE *f;
12164     char buf[MSG_SIZ];
12165
12166     if (strcmp(filename, "-") == 0) {
12167         f = stdin;
12168         title = "stdin";
12169     } else {
12170         f = fopen(filename, "rb");
12171         if (f == NULL) {
12172           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12173             DisplayError(buf, errno);
12174             return FALSE;
12175         }
12176     }
12177     if (fseek(f, 0, 0) == -1) {
12178         /* f is not seekable; probably a pipe */
12179         useList = FALSE;
12180     }
12181     if (useList && n == 0) {
12182         int error = GameListBuild(f);
12183         if (error) {
12184             DisplayError(_("Cannot build game list"), error);
12185         } else if (!ListEmpty(&gameList) &&
12186                    ((ListGame *) gameList.tailPred)->number > 1) {
12187             GameListPopUp(f, title);
12188             return TRUE;
12189         }
12190         GameListDestroy();
12191         n = 1;
12192     }
12193     if (n == 0) n = 1;
12194     return LoadGame(f, n, title, FALSE);
12195 }
12196
12197
12198 void
12199 MakeRegisteredMove ()
12200 {
12201     int fromX, fromY, toX, toY;
12202     char promoChar;
12203     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12204         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12205           case CMAIL_MOVE:
12206           case CMAIL_DRAW:
12207             if (appData.debugMode)
12208               fprintf(debugFP, "Restoring %s for game %d\n",
12209                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12210
12211             thinkOutput[0] = NULLCHAR;
12212             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12213             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12214             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12215             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12216             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12217             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12218             MakeMove(fromX, fromY, toX, toY, promoChar);
12219             ShowMove(fromX, fromY, toX, toY);
12220
12221             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12222               case MT_NONE:
12223               case MT_CHECK:
12224                 break;
12225
12226               case MT_CHECKMATE:
12227               case MT_STAINMATE:
12228                 if (WhiteOnMove(currentMove)) {
12229                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12230                 } else {
12231                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12232                 }
12233                 break;
12234
12235               case MT_STALEMATE:
12236                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12237                 break;
12238             }
12239
12240             break;
12241
12242           case CMAIL_RESIGN:
12243             if (WhiteOnMove(currentMove)) {
12244                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12245             } else {
12246                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12247             }
12248             break;
12249
12250           case CMAIL_ACCEPT:
12251             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12252             break;
12253
12254           default:
12255             break;
12256         }
12257     }
12258
12259     return;
12260 }
12261
12262 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12263 int
12264 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12265 {
12266     int retVal;
12267
12268     if (gameNumber > nCmailGames) {
12269         DisplayError(_("No more games in this message"), 0);
12270         return FALSE;
12271     }
12272     if (f == lastLoadGameFP) {
12273         int offset = gameNumber - lastLoadGameNumber;
12274         if (offset == 0) {
12275             cmailMsg[0] = NULLCHAR;
12276             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12277                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12278                 nCmailMovesRegistered--;
12279             }
12280             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12281             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12282                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12283             }
12284         } else {
12285             if (! RegisterMove()) return FALSE;
12286         }
12287     }
12288
12289     retVal = LoadGame(f, gameNumber, title, useList);
12290
12291     /* Make move registered during previous look at this game, if any */
12292     MakeRegisteredMove();
12293
12294     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12295         commentList[currentMove]
12296           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12297         DisplayComment(currentMove - 1, commentList[currentMove]);
12298     }
12299
12300     return retVal;
12301 }
12302
12303 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12304 int
12305 ReloadGame (int offset)
12306 {
12307     int gameNumber = lastLoadGameNumber + offset;
12308     if (lastLoadGameFP == NULL) {
12309         DisplayError(_("No game has been loaded yet"), 0);
12310         return FALSE;
12311     }
12312     if (gameNumber <= 0) {
12313         DisplayError(_("Can't back up any further"), 0);
12314         return FALSE;
12315     }
12316     if (cmailMsgLoaded) {
12317         return CmailLoadGame(lastLoadGameFP, gameNumber,
12318                              lastLoadGameTitle, lastLoadGameUseList);
12319     } else {
12320         return LoadGame(lastLoadGameFP, gameNumber,
12321                         lastLoadGameTitle, lastLoadGameUseList);
12322     }
12323 }
12324
12325 int keys[EmptySquare+1];
12326
12327 int
12328 PositionMatches (Board b1, Board b2)
12329 {
12330     int r, f, sum=0;
12331     switch(appData.searchMode) {
12332         case 1: return CompareWithRights(b1, b2);
12333         case 2:
12334             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12335                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12336             }
12337             return TRUE;
12338         case 3:
12339             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12340               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12341                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12342             }
12343             return sum==0;
12344         case 4:
12345             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12346                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12347             }
12348             return sum==0;
12349     }
12350     return TRUE;
12351 }
12352
12353 #define Q_PROMO  4
12354 #define Q_EP     3
12355 #define Q_BCASTL 2
12356 #define Q_WCASTL 1
12357
12358 int pieceList[256], quickBoard[256];
12359 ChessSquare pieceType[256] = { EmptySquare };
12360 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12361 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12362 int soughtTotal, turn;
12363 Boolean epOK, flipSearch;
12364
12365 typedef struct {
12366     unsigned char piece, to;
12367 } Move;
12368
12369 #define DSIZE (250000)
12370
12371 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12372 Move *moveDatabase = initialSpace;
12373 unsigned int movePtr, dataSize = DSIZE;
12374
12375 int
12376 MakePieceList (Board board, int *counts)
12377 {
12378     int r, f, n=Q_PROMO, total=0;
12379     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12380     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12381         int sq = f + (r<<4);
12382         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12383             quickBoard[sq] = ++n;
12384             pieceList[n] = sq;
12385             pieceType[n] = board[r][f];
12386             counts[board[r][f]]++;
12387             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12388             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12389             total++;
12390         }
12391     }
12392     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12393     return total;
12394 }
12395
12396 void
12397 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12398 {
12399     int sq = fromX + (fromY<<4);
12400     int piece = quickBoard[sq], rook;
12401     quickBoard[sq] = 0;
12402     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12403     if(piece == pieceList[1] && fromY == toY) {
12404       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12405         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12406         moveDatabase[movePtr++].piece = Q_WCASTL;
12407         quickBoard[sq] = piece;
12408         piece = quickBoard[from]; quickBoard[from] = 0;
12409         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12410       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12411         quickBoard[sq] = 0; // remove Rook
12412         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12413         moveDatabase[movePtr++].piece = Q_WCASTL;
12414         quickBoard[sq] = pieceList[1]; // put King
12415         piece = rook;
12416         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12417       }
12418     } else
12419     if(piece == pieceList[2] && fromY == toY) {
12420       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12421         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12422         moveDatabase[movePtr++].piece = Q_BCASTL;
12423         quickBoard[sq] = piece;
12424         piece = quickBoard[from]; quickBoard[from] = 0;
12425         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12426       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12427         quickBoard[sq] = 0; // remove Rook
12428         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12429         moveDatabase[movePtr++].piece = Q_BCASTL;
12430         quickBoard[sq] = pieceList[2]; // put King
12431         piece = rook;
12432         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12433       }
12434     } else
12435     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12436         quickBoard[(fromY<<4)+toX] = 0;
12437         moveDatabase[movePtr].piece = Q_EP;
12438         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12439         moveDatabase[movePtr].to = sq;
12440     } else
12441     if(promoPiece != pieceType[piece]) {
12442         moveDatabase[movePtr++].piece = Q_PROMO;
12443         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12444     }
12445     moveDatabase[movePtr].piece = piece;
12446     quickBoard[sq] = piece;
12447     movePtr++;
12448 }
12449
12450 int
12451 PackGame (Board board)
12452 {
12453     Move *newSpace = NULL;
12454     moveDatabase[movePtr].piece = 0; // terminate previous game
12455     if(movePtr > dataSize) {
12456         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12457         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12458         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12459         if(newSpace) {
12460             int i;
12461             Move *p = moveDatabase, *q = newSpace;
12462             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12463             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12464             moveDatabase = newSpace;
12465         } else { // calloc failed, we must be out of memory. Too bad...
12466             dataSize = 0; // prevent calloc events for all subsequent games
12467             return 0;     // and signal this one isn't cached
12468         }
12469     }
12470     movePtr++;
12471     MakePieceList(board, counts);
12472     return movePtr;
12473 }
12474
12475 int
12476 QuickCompare (Board board, int *minCounts, int *maxCounts)
12477 {   // compare according to search mode
12478     int r, f;
12479     switch(appData.searchMode)
12480     {
12481       case 1: // exact position match
12482         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12483         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12484             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12485         }
12486         break;
12487       case 2: // can have extra material on empty squares
12488         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12489             if(board[r][f] == EmptySquare) continue;
12490             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12491         }
12492         break;
12493       case 3: // material with exact Pawn structure
12494         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12495             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12496             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12497         } // fall through to material comparison
12498       case 4: // exact material
12499         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12500         break;
12501       case 6: // material range with given imbalance
12502         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12503         // fall through to range comparison
12504       case 5: // material range
12505         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12506     }
12507     return TRUE;
12508 }
12509
12510 int
12511 QuickScan (Board board, Move *move)
12512 {   // reconstruct game,and compare all positions in it
12513     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12514     do {
12515         int piece = move->piece;
12516         int to = move->to, from = pieceList[piece];
12517         if(found < 0) { // if already found just scan to game end for final piece count
12518           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12519            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12520            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12521                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12522             ) {
12523             static int lastCounts[EmptySquare+1];
12524             int i;
12525             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12526             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12527           } else stretch = 0;
12528           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12529           if(found >= 0 && !appData.minPieces) return found;
12530         }
12531         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12532           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12533           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12534             piece = (++move)->piece;
12535             from = pieceList[piece];
12536             counts[pieceType[piece]]--;
12537             pieceType[piece] = (ChessSquare) move->to;
12538             counts[move->to]++;
12539           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12540             counts[pieceType[quickBoard[to]]]--;
12541             quickBoard[to] = 0; total--;
12542             move++;
12543             continue;
12544           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12545             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12546             from  = pieceList[piece]; // so this must be King
12547             quickBoard[from] = 0;
12548             pieceList[piece] = to;
12549             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12550             quickBoard[from] = 0; // rook
12551             quickBoard[to] = piece;
12552             to = move->to; piece = move->piece;
12553             goto aftercastle;
12554           }
12555         }
12556         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12557         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12558         quickBoard[from] = 0;
12559       aftercastle:
12560         quickBoard[to] = piece;
12561         pieceList[piece] = to;
12562         cnt++; turn ^= 3;
12563         move++;
12564     } while(1);
12565 }
12566
12567 void
12568 InitSearch ()
12569 {
12570     int r, f;
12571     flipSearch = FALSE;
12572     CopyBoard(soughtBoard, boards[currentMove]);
12573     soughtTotal = MakePieceList(soughtBoard, maxSought);
12574     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12575     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12576     CopyBoard(reverseBoard, boards[currentMove]);
12577     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12578         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12579         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12580         reverseBoard[r][f] = piece;
12581     }
12582     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12583     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12584     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12585                  || (boards[currentMove][CASTLING][2] == NoRights ||
12586                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12587                  && (boards[currentMove][CASTLING][5] == NoRights ||
12588                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12589       ) {
12590         flipSearch = TRUE;
12591         CopyBoard(flipBoard, soughtBoard);
12592         CopyBoard(rotateBoard, reverseBoard);
12593         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12594             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12595             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12596         }
12597     }
12598     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12599     if(appData.searchMode >= 5) {
12600         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12601         MakePieceList(soughtBoard, minSought);
12602         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12603     }
12604     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12605         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12606 }
12607
12608 GameInfo dummyInfo;
12609 static int creatingBook;
12610
12611 int
12612 GameContainsPosition (FILE *f, ListGame *lg)
12613 {
12614     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12615     int fromX, fromY, toX, toY;
12616     char promoChar;
12617     static int initDone=FALSE;
12618
12619     // weed out games based on numerical tag comparison
12620     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12621     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12622     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12623     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12624     if(!initDone) {
12625         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12626         initDone = TRUE;
12627     }
12628     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12629     else CopyBoard(boards[scratch], initialPosition); // default start position
12630     if(lg->moves) {
12631         turn = btm + 1;
12632         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12633         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12634     }
12635     if(btm) plyNr++;
12636     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12637     fseek(f, lg->offset, 0);
12638     yynewfile(f);
12639     while(1) {
12640         yyboardindex = scratch;
12641         quickFlag = plyNr+1;
12642         next = Myylex();
12643         quickFlag = 0;
12644         switch(next) {
12645             case PGNTag:
12646                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12647             default:
12648                 continue;
12649
12650             case XBoardGame:
12651             case GNUChessGame:
12652                 if(plyNr) return -1; // after we have seen moves, this is for new game
12653               continue;
12654
12655             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12656             case ImpossibleMove:
12657             case WhiteWins: // game ends here with these four
12658             case BlackWins:
12659             case GameIsDrawn:
12660             case GameUnfinished:
12661                 return -1;
12662
12663             case IllegalMove:
12664                 if(appData.testLegality) return -1;
12665             case WhiteCapturesEnPassant:
12666             case BlackCapturesEnPassant:
12667             case WhitePromotion:
12668             case BlackPromotion:
12669             case WhiteNonPromotion:
12670             case BlackNonPromotion:
12671             case NormalMove:
12672             case FirstLeg:
12673             case WhiteKingSideCastle:
12674             case WhiteQueenSideCastle:
12675             case BlackKingSideCastle:
12676             case BlackQueenSideCastle:
12677             case WhiteKingSideCastleWild:
12678             case WhiteQueenSideCastleWild:
12679             case BlackKingSideCastleWild:
12680             case BlackQueenSideCastleWild:
12681             case WhiteHSideCastleFR:
12682             case WhiteASideCastleFR:
12683             case BlackHSideCastleFR:
12684             case BlackASideCastleFR:
12685                 fromX = currentMoveString[0] - AAA;
12686                 fromY = currentMoveString[1] - ONE;
12687                 toX = currentMoveString[2] - AAA;
12688                 toY = currentMoveString[3] - ONE;
12689                 promoChar = currentMoveString[4];
12690                 break;
12691             case WhiteDrop:
12692             case BlackDrop:
12693                 fromX = next == WhiteDrop ?
12694                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12695                   (int) CharToPiece(ToLower(currentMoveString[0]));
12696                 fromY = DROP_RANK;
12697                 toX = currentMoveString[2] - AAA;
12698                 toY = currentMoveString[3] - ONE;
12699                 promoChar = 0;
12700                 break;
12701         }
12702         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12703         plyNr++;
12704         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12705         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12706         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12707         if(appData.findMirror) {
12708             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12709             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12710         }
12711     }
12712 }
12713
12714 /* Load the nth game from open file f */
12715 int
12716 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12717 {
12718     ChessMove cm;
12719     char buf[MSG_SIZ];
12720     int gn = gameNumber;
12721     ListGame *lg = NULL;
12722     int numPGNTags = 0;
12723     int err, pos = -1;
12724     GameMode oldGameMode;
12725     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12726     char oldName[MSG_SIZ];
12727
12728     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12729
12730     if (appData.debugMode)
12731         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12732
12733     if (gameMode == Training )
12734         SetTrainingModeOff();
12735
12736     oldGameMode = gameMode;
12737     if (gameMode != BeginningOfGame) {
12738       Reset(FALSE, TRUE);
12739     }
12740     killX = killY = -1; // [HGM] lion: in case we did not Reset
12741
12742     gameFileFP = f;
12743     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12744         fclose(lastLoadGameFP);
12745     }
12746
12747     if (useList) {
12748         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12749
12750         if (lg) {
12751             fseek(f, lg->offset, 0);
12752             GameListHighlight(gameNumber);
12753             pos = lg->position;
12754             gn = 1;
12755         }
12756         else {
12757             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12758               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12759             else
12760             DisplayError(_("Game number out of range"), 0);
12761             return FALSE;
12762         }
12763     } else {
12764         GameListDestroy();
12765         if (fseek(f, 0, 0) == -1) {
12766             if (f == lastLoadGameFP ?
12767                 gameNumber == lastLoadGameNumber + 1 :
12768                 gameNumber == 1) {
12769                 gn = 1;
12770             } else {
12771                 DisplayError(_("Can't seek on game file"), 0);
12772                 return FALSE;
12773             }
12774         }
12775     }
12776     lastLoadGameFP = f;
12777     lastLoadGameNumber = gameNumber;
12778     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12779     lastLoadGameUseList = useList;
12780
12781     yynewfile(f);
12782
12783     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12784       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12785                 lg->gameInfo.black);
12786             DisplayTitle(buf);
12787     } else if (*title != NULLCHAR) {
12788         if (gameNumber > 1) {
12789           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12790             DisplayTitle(buf);
12791         } else {
12792             DisplayTitle(title);
12793         }
12794     }
12795
12796     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12797         gameMode = PlayFromGameFile;
12798         ModeHighlight();
12799     }
12800
12801     currentMove = forwardMostMove = backwardMostMove = 0;
12802     CopyBoard(boards[0], initialPosition);
12803     StopClocks();
12804
12805     /*
12806      * Skip the first gn-1 games in the file.
12807      * Also skip over anything that precedes an identifiable
12808      * start of game marker, to avoid being confused by
12809      * garbage at the start of the file.  Currently
12810      * recognized start of game markers are the move number "1",
12811      * the pattern "gnuchess .* game", the pattern
12812      * "^[#;%] [^ ]* game file", and a PGN tag block.
12813      * A game that starts with one of the latter two patterns
12814      * will also have a move number 1, possibly
12815      * following a position diagram.
12816      * 5-4-02: Let's try being more lenient and allowing a game to
12817      * start with an unnumbered move.  Does that break anything?
12818      */
12819     cm = lastLoadGameStart = EndOfFile;
12820     while (gn > 0) {
12821         yyboardindex = forwardMostMove;
12822         cm = (ChessMove) Myylex();
12823         switch (cm) {
12824           case EndOfFile:
12825             if (cmailMsgLoaded) {
12826                 nCmailGames = CMAIL_MAX_GAMES - gn;
12827             } else {
12828                 Reset(TRUE, TRUE);
12829                 DisplayError(_("Game not found in file"), 0);
12830             }
12831             return FALSE;
12832
12833           case GNUChessGame:
12834           case XBoardGame:
12835             gn--;
12836             lastLoadGameStart = cm;
12837             break;
12838
12839           case MoveNumberOne:
12840             switch (lastLoadGameStart) {
12841               case GNUChessGame:
12842               case XBoardGame:
12843               case PGNTag:
12844                 break;
12845               case MoveNumberOne:
12846               case EndOfFile:
12847                 gn--;           /* count this game */
12848                 lastLoadGameStart = cm;
12849                 break;
12850               default:
12851                 /* impossible */
12852                 break;
12853             }
12854             break;
12855
12856           case PGNTag:
12857             switch (lastLoadGameStart) {
12858               case GNUChessGame:
12859               case PGNTag:
12860               case MoveNumberOne:
12861               case EndOfFile:
12862                 gn--;           /* count this game */
12863                 lastLoadGameStart = cm;
12864                 break;
12865               case XBoardGame:
12866                 lastLoadGameStart = cm; /* game counted already */
12867                 break;
12868               default:
12869                 /* impossible */
12870                 break;
12871             }
12872             if (gn > 0) {
12873                 do {
12874                     yyboardindex = forwardMostMove;
12875                     cm = (ChessMove) Myylex();
12876                 } while (cm == PGNTag || cm == Comment);
12877             }
12878             break;
12879
12880           case WhiteWins:
12881           case BlackWins:
12882           case GameIsDrawn:
12883             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12884                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12885                     != CMAIL_OLD_RESULT) {
12886                     nCmailResults ++ ;
12887                     cmailResult[  CMAIL_MAX_GAMES
12888                                 - gn - 1] = CMAIL_OLD_RESULT;
12889                 }
12890             }
12891             break;
12892
12893           case NormalMove:
12894           case FirstLeg:
12895             /* Only a NormalMove can be at the start of a game
12896              * without a position diagram. */
12897             if (lastLoadGameStart == EndOfFile ) {
12898               gn--;
12899               lastLoadGameStart = MoveNumberOne;
12900             }
12901             break;
12902
12903           default:
12904             break;
12905         }
12906     }
12907
12908     if (appData.debugMode)
12909       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12910
12911     if (cm == XBoardGame) {
12912         /* Skip any header junk before position diagram and/or move 1 */
12913         for (;;) {
12914             yyboardindex = forwardMostMove;
12915             cm = (ChessMove) Myylex();
12916
12917             if (cm == EndOfFile ||
12918                 cm == GNUChessGame || cm == XBoardGame) {
12919                 /* Empty game; pretend end-of-file and handle later */
12920                 cm = EndOfFile;
12921                 break;
12922             }
12923
12924             if (cm == MoveNumberOne || cm == PositionDiagram ||
12925                 cm == PGNTag || cm == Comment)
12926               break;
12927         }
12928     } else if (cm == GNUChessGame) {
12929         if (gameInfo.event != NULL) {
12930             free(gameInfo.event);
12931         }
12932         gameInfo.event = StrSave(yy_text);
12933     }
12934
12935     startedFromSetupPosition = FALSE;
12936     while (cm == PGNTag) {
12937         if (appData.debugMode)
12938           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12939         err = ParsePGNTag(yy_text, &gameInfo);
12940         if (!err) numPGNTags++;
12941
12942         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12943         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12944             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12945             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12946             InitPosition(TRUE);
12947             oldVariant = gameInfo.variant;
12948             if (appData.debugMode)
12949               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12950         }
12951
12952
12953         if (gameInfo.fen != NULL) {
12954           Board initial_position;
12955           startedFromSetupPosition = TRUE;
12956           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12957             Reset(TRUE, TRUE);
12958             DisplayError(_("Bad FEN position in file"), 0);
12959             return FALSE;
12960           }
12961           CopyBoard(boards[0], initial_position);
12962           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12963             CopyBoard(initialPosition, initial_position);
12964           if (blackPlaysFirst) {
12965             currentMove = forwardMostMove = backwardMostMove = 1;
12966             CopyBoard(boards[1], initial_position);
12967             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12968             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12969             timeRemaining[0][1] = whiteTimeRemaining;
12970             timeRemaining[1][1] = blackTimeRemaining;
12971             if (commentList[0] != NULL) {
12972               commentList[1] = commentList[0];
12973               commentList[0] = NULL;
12974             }
12975           } else {
12976             currentMove = forwardMostMove = backwardMostMove = 0;
12977           }
12978           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12979           {   int i;
12980               initialRulePlies = FENrulePlies;
12981               for( i=0; i< nrCastlingRights; i++ )
12982                   initialRights[i] = initial_position[CASTLING][i];
12983           }
12984           yyboardindex = forwardMostMove;
12985           free(gameInfo.fen);
12986           gameInfo.fen = NULL;
12987         }
12988
12989         yyboardindex = forwardMostMove;
12990         cm = (ChessMove) Myylex();
12991
12992         /* Handle comments interspersed among the tags */
12993         while (cm == Comment) {
12994             char *p;
12995             if (appData.debugMode)
12996               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12997             p = yy_text;
12998             AppendComment(currentMove, p, FALSE);
12999             yyboardindex = forwardMostMove;
13000             cm = (ChessMove) Myylex();
13001         }
13002     }
13003
13004     /* don't rely on existence of Event tag since if game was
13005      * pasted from clipboard the Event tag may not exist
13006      */
13007     if (numPGNTags > 0){
13008         char *tags;
13009         if (gameInfo.variant == VariantNormal) {
13010           VariantClass v = StringToVariant(gameInfo.event);
13011           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13012           if(v < VariantShogi) gameInfo.variant = v;
13013         }
13014         if (!matchMode) {
13015           if( appData.autoDisplayTags ) {
13016             tags = PGNTags(&gameInfo);
13017             TagsPopUp(tags, CmailMsg());
13018             free(tags);
13019           }
13020         }
13021     } else {
13022         /* Make something up, but don't display it now */
13023         SetGameInfo();
13024         TagsPopDown();
13025     }
13026
13027     if (cm == PositionDiagram) {
13028         int i, j;
13029         char *p;
13030         Board initial_position;
13031
13032         if (appData.debugMode)
13033           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13034
13035         if (!startedFromSetupPosition) {
13036             p = yy_text;
13037             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13038               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13039                 switch (*p) {
13040                   case '{':
13041                   case '[':
13042                   case '-':
13043                   case ' ':
13044                   case '\t':
13045                   case '\n':
13046                   case '\r':
13047                     break;
13048                   default:
13049                     initial_position[i][j++] = CharToPiece(*p);
13050                     break;
13051                 }
13052             while (*p == ' ' || *p == '\t' ||
13053                    *p == '\n' || *p == '\r') p++;
13054
13055             if (strncmp(p, "black", strlen("black"))==0)
13056               blackPlaysFirst = TRUE;
13057             else
13058               blackPlaysFirst = FALSE;
13059             startedFromSetupPosition = TRUE;
13060
13061             CopyBoard(boards[0], initial_position);
13062             if (blackPlaysFirst) {
13063                 currentMove = forwardMostMove = backwardMostMove = 1;
13064                 CopyBoard(boards[1], initial_position);
13065                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13066                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13067                 timeRemaining[0][1] = whiteTimeRemaining;
13068                 timeRemaining[1][1] = blackTimeRemaining;
13069                 if (commentList[0] != NULL) {
13070                     commentList[1] = commentList[0];
13071                     commentList[0] = NULL;
13072                 }
13073             } else {
13074                 currentMove = forwardMostMove = backwardMostMove = 0;
13075             }
13076         }
13077         yyboardindex = forwardMostMove;
13078         cm = (ChessMove) Myylex();
13079     }
13080
13081   if(!creatingBook) {
13082     if (first.pr == NoProc) {
13083         StartChessProgram(&first);
13084     }
13085     InitChessProgram(&first, FALSE);
13086     if(gameInfo.variant == VariantUnknown && *oldName) {
13087         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13088         gameInfo.variant = v;
13089     }
13090     SendToProgram("force\n", &first);
13091     if (startedFromSetupPosition) {
13092         SendBoard(&first, forwardMostMove);
13093     if (appData.debugMode) {
13094         fprintf(debugFP, "Load Game\n");
13095     }
13096         DisplayBothClocks();
13097     }
13098   }
13099
13100     /* [HGM] server: flag to write setup moves in broadcast file as one */
13101     loadFlag = appData.suppressLoadMoves;
13102
13103     while (cm == Comment) {
13104         char *p;
13105         if (appData.debugMode)
13106           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13107         p = yy_text;
13108         AppendComment(currentMove, p, FALSE);
13109         yyboardindex = forwardMostMove;
13110         cm = (ChessMove) Myylex();
13111     }
13112
13113     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13114         cm == WhiteWins || cm == BlackWins ||
13115         cm == GameIsDrawn || cm == GameUnfinished) {
13116         DisplayMessage("", _("No moves in game"));
13117         if (cmailMsgLoaded) {
13118             if (appData.debugMode)
13119               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13120             ClearHighlights();
13121             flipView = FALSE;
13122         }
13123         DrawPosition(FALSE, boards[currentMove]);
13124         DisplayBothClocks();
13125         gameMode = EditGame;
13126         ModeHighlight();
13127         gameFileFP = NULL;
13128         cmailOldMove = 0;
13129         return TRUE;
13130     }
13131
13132     // [HGM] PV info: routine tests if comment empty
13133     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13134         DisplayComment(currentMove - 1, commentList[currentMove]);
13135     }
13136     if (!matchMode && appData.timeDelay != 0)
13137       DrawPosition(FALSE, boards[currentMove]);
13138
13139     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13140       programStats.ok_to_send = 1;
13141     }
13142
13143     /* if the first token after the PGN tags is a move
13144      * and not move number 1, retrieve it from the parser
13145      */
13146     if (cm != MoveNumberOne)
13147         LoadGameOneMove(cm);
13148
13149     /* load the remaining moves from the file */
13150     while (LoadGameOneMove(EndOfFile)) {
13151       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13152       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13153     }
13154
13155     /* rewind to the start of the game */
13156     currentMove = backwardMostMove;
13157
13158     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13159
13160     if (oldGameMode == AnalyzeFile) {
13161       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13162       AnalyzeFileEvent();
13163     } else
13164     if (oldGameMode == AnalyzeMode) {
13165       AnalyzeFileEvent();
13166     }
13167
13168     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13169         long int w, b; // [HGM] adjourn: restore saved clock times
13170         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13171         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13172             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13173             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13174         }
13175     }
13176
13177     if(creatingBook) return TRUE;
13178     if (!matchMode && pos > 0) {
13179         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13180     } else
13181     if (matchMode || appData.timeDelay == 0) {
13182       ToEndEvent();
13183     } else if (appData.timeDelay > 0) {
13184       AutoPlayGameLoop();
13185     }
13186
13187     if (appData.debugMode)
13188         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13189
13190     loadFlag = 0; /* [HGM] true game starts */
13191     return TRUE;
13192 }
13193
13194 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13195 int
13196 ReloadPosition (int offset)
13197 {
13198     int positionNumber = lastLoadPositionNumber + offset;
13199     if (lastLoadPositionFP == NULL) {
13200         DisplayError(_("No position has been loaded yet"), 0);
13201         return FALSE;
13202     }
13203     if (positionNumber <= 0) {
13204         DisplayError(_("Can't back up any further"), 0);
13205         return FALSE;
13206     }
13207     return LoadPosition(lastLoadPositionFP, positionNumber,
13208                         lastLoadPositionTitle);
13209 }
13210
13211 /* Load the nth position from the given file */
13212 int
13213 LoadPositionFromFile (char *filename, int n, char *title)
13214 {
13215     FILE *f;
13216     char buf[MSG_SIZ];
13217
13218     if (strcmp(filename, "-") == 0) {
13219         return LoadPosition(stdin, n, "stdin");
13220     } else {
13221         f = fopen(filename, "rb");
13222         if (f == NULL) {
13223             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13224             DisplayError(buf, errno);
13225             return FALSE;
13226         } else {
13227             return LoadPosition(f, n, title);
13228         }
13229     }
13230 }
13231
13232 /* Load the nth position from the given open file, and close it */
13233 int
13234 LoadPosition (FILE *f, int positionNumber, char *title)
13235 {
13236     char *p, line[MSG_SIZ];
13237     Board initial_position;
13238     int i, j, fenMode, pn;
13239
13240     if (gameMode == Training )
13241         SetTrainingModeOff();
13242
13243     if (gameMode != BeginningOfGame) {
13244         Reset(FALSE, TRUE);
13245     }
13246     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13247         fclose(lastLoadPositionFP);
13248     }
13249     if (positionNumber == 0) positionNumber = 1;
13250     lastLoadPositionFP = f;
13251     lastLoadPositionNumber = positionNumber;
13252     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13253     if (first.pr == NoProc && !appData.noChessProgram) {
13254       StartChessProgram(&first);
13255       InitChessProgram(&first, FALSE);
13256     }
13257     pn = positionNumber;
13258     if (positionNumber < 0) {
13259         /* Negative position number means to seek to that byte offset */
13260         if (fseek(f, -positionNumber, 0) == -1) {
13261             DisplayError(_("Can't seek on position file"), 0);
13262             return FALSE;
13263         };
13264         pn = 1;
13265     } else {
13266         if (fseek(f, 0, 0) == -1) {
13267             if (f == lastLoadPositionFP ?
13268                 positionNumber == lastLoadPositionNumber + 1 :
13269                 positionNumber == 1) {
13270                 pn = 1;
13271             } else {
13272                 DisplayError(_("Can't seek on position file"), 0);
13273                 return FALSE;
13274             }
13275         }
13276     }
13277     /* See if this file is FEN or old-style xboard */
13278     if (fgets(line, MSG_SIZ, f) == NULL) {
13279         DisplayError(_("Position not found in file"), 0);
13280         return FALSE;
13281     }
13282     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13283     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13284
13285     if (pn >= 2) {
13286         if (fenMode || line[0] == '#') pn--;
13287         while (pn > 0) {
13288             /* skip positions before number pn */
13289             if (fgets(line, MSG_SIZ, f) == NULL) {
13290                 Reset(TRUE, TRUE);
13291                 DisplayError(_("Position not found in file"), 0);
13292                 return FALSE;
13293             }
13294             if (fenMode || line[0] == '#') pn--;
13295         }
13296     }
13297
13298     if (fenMode) {
13299         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13300             DisplayError(_("Bad FEN position in file"), 0);
13301             return FALSE;
13302         }
13303     } else {
13304         (void) fgets(line, MSG_SIZ, f);
13305         (void) fgets(line, MSG_SIZ, f);
13306
13307         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13308             (void) fgets(line, MSG_SIZ, f);
13309             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13310                 if (*p == ' ')
13311                   continue;
13312                 initial_position[i][j++] = CharToPiece(*p);
13313             }
13314         }
13315
13316         blackPlaysFirst = FALSE;
13317         if (!feof(f)) {
13318             (void) fgets(line, MSG_SIZ, f);
13319             if (strncmp(line, "black", strlen("black"))==0)
13320               blackPlaysFirst = TRUE;
13321         }
13322     }
13323     startedFromSetupPosition = TRUE;
13324
13325     CopyBoard(boards[0], initial_position);
13326     if (blackPlaysFirst) {
13327         currentMove = forwardMostMove = backwardMostMove = 1;
13328         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13329         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13330         CopyBoard(boards[1], initial_position);
13331         DisplayMessage("", _("Black to play"));
13332     } else {
13333         currentMove = forwardMostMove = backwardMostMove = 0;
13334         DisplayMessage("", _("White to play"));
13335     }
13336     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13337     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13338         SendToProgram("force\n", &first);
13339         SendBoard(&first, forwardMostMove);
13340     }
13341     if (appData.debugMode) {
13342 int i, j;
13343   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13344   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13345         fprintf(debugFP, "Load Position\n");
13346     }
13347
13348     if (positionNumber > 1) {
13349       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13350         DisplayTitle(line);
13351     } else {
13352         DisplayTitle(title);
13353     }
13354     gameMode = EditGame;
13355     ModeHighlight();
13356     ResetClocks();
13357     timeRemaining[0][1] = whiteTimeRemaining;
13358     timeRemaining[1][1] = blackTimeRemaining;
13359     DrawPosition(FALSE, boards[currentMove]);
13360
13361     return TRUE;
13362 }
13363
13364
13365 void
13366 CopyPlayerNameIntoFileName (char **dest, char *src)
13367 {
13368     while (*src != NULLCHAR && *src != ',') {
13369         if (*src == ' ') {
13370             *(*dest)++ = '_';
13371             src++;
13372         } else {
13373             *(*dest)++ = *src++;
13374         }
13375     }
13376 }
13377
13378 char *
13379 DefaultFileName (char *ext)
13380 {
13381     static char def[MSG_SIZ];
13382     char *p;
13383
13384     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13385         p = def;
13386         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13387         *p++ = '-';
13388         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13389         *p++ = '.';
13390         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13391     } else {
13392         def[0] = NULLCHAR;
13393     }
13394     return def;
13395 }
13396
13397 /* Save the current game to the given file */
13398 int
13399 SaveGameToFile (char *filename, int append)
13400 {
13401     FILE *f;
13402     char buf[MSG_SIZ];
13403     int result, i, t,tot=0;
13404
13405     if (strcmp(filename, "-") == 0) {
13406         return SaveGame(stdout, 0, NULL);
13407     } else {
13408         for(i=0; i<10; i++) { // upto 10 tries
13409              f = fopen(filename, append ? "a" : "w");
13410              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13411              if(f || errno != 13) break;
13412              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13413              tot += t;
13414         }
13415         if (f == NULL) {
13416             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13417             DisplayError(buf, errno);
13418             return FALSE;
13419         } else {
13420             safeStrCpy(buf, lastMsg, MSG_SIZ);
13421             DisplayMessage(_("Waiting for access to save file"), "");
13422             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13423             DisplayMessage(_("Saving game"), "");
13424             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13425             result = SaveGame(f, 0, NULL);
13426             DisplayMessage(buf, "");
13427             return result;
13428         }
13429     }
13430 }
13431
13432 char *
13433 SavePart (char *str)
13434 {
13435     static char buf[MSG_SIZ];
13436     char *p;
13437
13438     p = strchr(str, ' ');
13439     if (p == NULL) return str;
13440     strncpy(buf, str, p - str);
13441     buf[p - str] = NULLCHAR;
13442     return buf;
13443 }
13444
13445 #define PGN_MAX_LINE 75
13446
13447 #define PGN_SIDE_WHITE  0
13448 #define PGN_SIDE_BLACK  1
13449
13450 static int
13451 FindFirstMoveOutOfBook (int side)
13452 {
13453     int result = -1;
13454
13455     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13456         int index = backwardMostMove;
13457         int has_book_hit = 0;
13458
13459         if( (index % 2) != side ) {
13460             index++;
13461         }
13462
13463         while( index < forwardMostMove ) {
13464             /* Check to see if engine is in book */
13465             int depth = pvInfoList[index].depth;
13466             int score = pvInfoList[index].score;
13467             int in_book = 0;
13468
13469             if( depth <= 2 ) {
13470                 in_book = 1;
13471             }
13472             else if( score == 0 && depth == 63 ) {
13473                 in_book = 1; /* Zappa */
13474             }
13475             else if( score == 2 && depth == 99 ) {
13476                 in_book = 1; /* Abrok */
13477             }
13478
13479             has_book_hit += in_book;
13480
13481             if( ! in_book ) {
13482                 result = index;
13483
13484                 break;
13485             }
13486
13487             index += 2;
13488         }
13489     }
13490
13491     return result;
13492 }
13493
13494 void
13495 GetOutOfBookInfo (char * buf)
13496 {
13497     int oob[2];
13498     int i;
13499     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13500
13501     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13502     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13503
13504     *buf = '\0';
13505
13506     if( oob[0] >= 0 || oob[1] >= 0 ) {
13507         for( i=0; i<2; i++ ) {
13508             int idx = oob[i];
13509
13510             if( idx >= 0 ) {
13511                 if( i > 0 && oob[0] >= 0 ) {
13512                     strcat( buf, "   " );
13513                 }
13514
13515                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13516                 sprintf( buf+strlen(buf), "%s%.2f",
13517                     pvInfoList[idx].score >= 0 ? "+" : "",
13518                     pvInfoList[idx].score / 100.0 );
13519             }
13520         }
13521     }
13522 }
13523
13524 /* Save game in PGN style */
13525 static void
13526 SaveGamePGN2 (FILE *f)
13527 {
13528     int i, offset, linelen, newblock;
13529 //    char *movetext;
13530     char numtext[32];
13531     int movelen, numlen, blank;
13532     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13533
13534     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13535
13536     PrintPGNTags(f, &gameInfo);
13537
13538     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13539
13540     if (backwardMostMove > 0 || startedFromSetupPosition) {
13541         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13542         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13543         fprintf(f, "\n{--------------\n");
13544         PrintPosition(f, backwardMostMove);
13545         fprintf(f, "--------------}\n");
13546         free(fen);
13547     }
13548     else {
13549         /* [AS] Out of book annotation */
13550         if( appData.saveOutOfBookInfo ) {
13551             char buf[64];
13552
13553             GetOutOfBookInfo( buf );
13554
13555             if( buf[0] != '\0' ) {
13556                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13557             }
13558         }
13559
13560         fprintf(f, "\n");
13561     }
13562
13563     i = backwardMostMove;
13564     linelen = 0;
13565     newblock = TRUE;
13566
13567     while (i < forwardMostMove) {
13568         /* Print comments preceding this move */
13569         if (commentList[i] != NULL) {
13570             if (linelen > 0) fprintf(f, "\n");
13571             fprintf(f, "%s", commentList[i]);
13572             linelen = 0;
13573             newblock = TRUE;
13574         }
13575
13576         /* Format move number */
13577         if ((i % 2) == 0)
13578           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13579         else
13580           if (newblock)
13581             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13582           else
13583             numtext[0] = NULLCHAR;
13584
13585         numlen = strlen(numtext);
13586         newblock = FALSE;
13587
13588         /* Print move number */
13589         blank = linelen > 0 && numlen > 0;
13590         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13591             fprintf(f, "\n");
13592             linelen = 0;
13593             blank = 0;
13594         }
13595         if (blank) {
13596             fprintf(f, " ");
13597             linelen++;
13598         }
13599         fprintf(f, "%s", numtext);
13600         linelen += numlen;
13601
13602         /* Get move */
13603         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13604         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13605
13606         /* Print move */
13607         blank = linelen > 0 && movelen > 0;
13608         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13609             fprintf(f, "\n");
13610             linelen = 0;
13611             blank = 0;
13612         }
13613         if (blank) {
13614             fprintf(f, " ");
13615             linelen++;
13616         }
13617         fprintf(f, "%s", move_buffer);
13618         linelen += movelen;
13619
13620         /* [AS] Add PV info if present */
13621         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13622             /* [HGM] add time */
13623             char buf[MSG_SIZ]; int seconds;
13624
13625             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13626
13627             if( seconds <= 0)
13628               buf[0] = 0;
13629             else
13630               if( seconds < 30 )
13631                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13632               else
13633                 {
13634                   seconds = (seconds + 4)/10; // round to full seconds
13635                   if( seconds < 60 )
13636                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13637                   else
13638                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13639                 }
13640
13641             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13642                       pvInfoList[i].score >= 0 ? "+" : "",
13643                       pvInfoList[i].score / 100.0,
13644                       pvInfoList[i].depth,
13645                       buf );
13646
13647             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13648
13649             /* Print score/depth */
13650             blank = linelen > 0 && movelen > 0;
13651             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13652                 fprintf(f, "\n");
13653                 linelen = 0;
13654                 blank = 0;
13655             }
13656             if (blank) {
13657                 fprintf(f, " ");
13658                 linelen++;
13659             }
13660             fprintf(f, "%s", move_buffer);
13661             linelen += movelen;
13662         }
13663
13664         i++;
13665     }
13666
13667     /* Start a new line */
13668     if (linelen > 0) fprintf(f, "\n");
13669
13670     /* Print comments after last move */
13671     if (commentList[i] != NULL) {
13672         fprintf(f, "%s\n", commentList[i]);
13673     }
13674
13675     /* Print result */
13676     if (gameInfo.resultDetails != NULL &&
13677         gameInfo.resultDetails[0] != NULLCHAR) {
13678         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13679         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13680            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13681             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13682         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13683     } else {
13684         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13685     }
13686 }
13687
13688 /* Save game in PGN style and close the file */
13689 int
13690 SaveGamePGN (FILE *f)
13691 {
13692     SaveGamePGN2(f);
13693     fclose(f);
13694     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13695     return TRUE;
13696 }
13697
13698 /* Save game in old style and close the file */
13699 int
13700 SaveGameOldStyle (FILE *f)
13701 {
13702     int i, offset;
13703     time_t tm;
13704
13705     tm = time((time_t *) NULL);
13706
13707     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13708     PrintOpponents(f);
13709
13710     if (backwardMostMove > 0 || startedFromSetupPosition) {
13711         fprintf(f, "\n[--------------\n");
13712         PrintPosition(f, backwardMostMove);
13713         fprintf(f, "--------------]\n");
13714     } else {
13715         fprintf(f, "\n");
13716     }
13717
13718     i = backwardMostMove;
13719     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13720
13721     while (i < forwardMostMove) {
13722         if (commentList[i] != NULL) {
13723             fprintf(f, "[%s]\n", commentList[i]);
13724         }
13725
13726         if ((i % 2) == 1) {
13727             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13728             i++;
13729         } else {
13730             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13731             i++;
13732             if (commentList[i] != NULL) {
13733                 fprintf(f, "\n");
13734                 continue;
13735             }
13736             if (i >= forwardMostMove) {
13737                 fprintf(f, "\n");
13738                 break;
13739             }
13740             fprintf(f, "%s\n", parseList[i]);
13741             i++;
13742         }
13743     }
13744
13745     if (commentList[i] != NULL) {
13746         fprintf(f, "[%s]\n", commentList[i]);
13747     }
13748
13749     /* This isn't really the old style, but it's close enough */
13750     if (gameInfo.resultDetails != NULL &&
13751         gameInfo.resultDetails[0] != NULLCHAR) {
13752         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13753                 gameInfo.resultDetails);
13754     } else {
13755         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13756     }
13757
13758     fclose(f);
13759     return TRUE;
13760 }
13761
13762 /* Save the current game to open file f and close the file */
13763 int
13764 SaveGame (FILE *f, int dummy, char *dummy2)
13765 {
13766     if (gameMode == EditPosition) EditPositionDone(TRUE);
13767     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13768     if (appData.oldSaveStyle)
13769       return SaveGameOldStyle(f);
13770     else
13771       return SaveGamePGN(f);
13772 }
13773
13774 /* Save the current position to the given file */
13775 int
13776 SavePositionToFile (char *filename)
13777 {
13778     FILE *f;
13779     char buf[MSG_SIZ];
13780
13781     if (strcmp(filename, "-") == 0) {
13782         return SavePosition(stdout, 0, NULL);
13783     } else {
13784         f = fopen(filename, "a");
13785         if (f == NULL) {
13786             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13787             DisplayError(buf, errno);
13788             return FALSE;
13789         } else {
13790             safeStrCpy(buf, lastMsg, MSG_SIZ);
13791             DisplayMessage(_("Waiting for access to save file"), "");
13792             flock(fileno(f), LOCK_EX); // [HGM] lock
13793             DisplayMessage(_("Saving position"), "");
13794             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13795             SavePosition(f, 0, NULL);
13796             DisplayMessage(buf, "");
13797             return TRUE;
13798         }
13799     }
13800 }
13801
13802 /* Save the current position to the given open file and close the file */
13803 int
13804 SavePosition (FILE *f, int dummy, char *dummy2)
13805 {
13806     time_t tm;
13807     char *fen;
13808
13809     if (gameMode == EditPosition) EditPositionDone(TRUE);
13810     if (appData.oldSaveStyle) {
13811         tm = time((time_t *) NULL);
13812
13813         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13814         PrintOpponents(f);
13815         fprintf(f, "[--------------\n");
13816         PrintPosition(f, currentMove);
13817         fprintf(f, "--------------]\n");
13818     } else {
13819         fen = PositionToFEN(currentMove, NULL, 1);
13820         fprintf(f, "%s\n", fen);
13821         free(fen);
13822     }
13823     fclose(f);
13824     return TRUE;
13825 }
13826
13827 void
13828 ReloadCmailMsgEvent (int unregister)
13829 {
13830 #if !WIN32
13831     static char *inFilename = NULL;
13832     static char *outFilename;
13833     int i;
13834     struct stat inbuf, outbuf;
13835     int status;
13836
13837     /* Any registered moves are unregistered if unregister is set, */
13838     /* i.e. invoked by the signal handler */
13839     if (unregister) {
13840         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13841             cmailMoveRegistered[i] = FALSE;
13842             if (cmailCommentList[i] != NULL) {
13843                 free(cmailCommentList[i]);
13844                 cmailCommentList[i] = NULL;
13845             }
13846         }
13847         nCmailMovesRegistered = 0;
13848     }
13849
13850     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13851         cmailResult[i] = CMAIL_NOT_RESULT;
13852     }
13853     nCmailResults = 0;
13854
13855     if (inFilename == NULL) {
13856         /* Because the filenames are static they only get malloced once  */
13857         /* and they never get freed                                      */
13858         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13859         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13860
13861         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13862         sprintf(outFilename, "%s.out", appData.cmailGameName);
13863     }
13864
13865     status = stat(outFilename, &outbuf);
13866     if (status < 0) {
13867         cmailMailedMove = FALSE;
13868     } else {
13869         status = stat(inFilename, &inbuf);
13870         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13871     }
13872
13873     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13874        counts the games, notes how each one terminated, etc.
13875
13876        It would be nice to remove this kludge and instead gather all
13877        the information while building the game list.  (And to keep it
13878        in the game list nodes instead of having a bunch of fixed-size
13879        parallel arrays.)  Note this will require getting each game's
13880        termination from the PGN tags, as the game list builder does
13881        not process the game moves.  --mann
13882        */
13883     cmailMsgLoaded = TRUE;
13884     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13885
13886     /* Load first game in the file or popup game menu */
13887     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13888
13889 #endif /* !WIN32 */
13890     return;
13891 }
13892
13893 int
13894 RegisterMove ()
13895 {
13896     FILE *f;
13897     char string[MSG_SIZ];
13898
13899     if (   cmailMailedMove
13900         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13901         return TRUE;            /* Allow free viewing  */
13902     }
13903
13904     /* Unregister move to ensure that we don't leave RegisterMove        */
13905     /* with the move registered when the conditions for registering no   */
13906     /* longer hold                                                       */
13907     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13908         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13909         nCmailMovesRegistered --;
13910
13911         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13912           {
13913               free(cmailCommentList[lastLoadGameNumber - 1]);
13914               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13915           }
13916     }
13917
13918     if (cmailOldMove == -1) {
13919         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13920         return FALSE;
13921     }
13922
13923     if (currentMove > cmailOldMove + 1) {
13924         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13925         return FALSE;
13926     }
13927
13928     if (currentMove < cmailOldMove) {
13929         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13930         return FALSE;
13931     }
13932
13933     if (forwardMostMove > currentMove) {
13934         /* Silently truncate extra moves */
13935         TruncateGame();
13936     }
13937
13938     if (   (currentMove == cmailOldMove + 1)
13939         || (   (currentMove == cmailOldMove)
13940             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13941                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13942         if (gameInfo.result != GameUnfinished) {
13943             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13944         }
13945
13946         if (commentList[currentMove] != NULL) {
13947             cmailCommentList[lastLoadGameNumber - 1]
13948               = StrSave(commentList[currentMove]);
13949         }
13950         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13951
13952         if (appData.debugMode)
13953           fprintf(debugFP, "Saving %s for game %d\n",
13954                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13955
13956         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13957
13958         f = fopen(string, "w");
13959         if (appData.oldSaveStyle) {
13960             SaveGameOldStyle(f); /* also closes the file */
13961
13962             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13963             f = fopen(string, "w");
13964             SavePosition(f, 0, NULL); /* also closes the file */
13965         } else {
13966             fprintf(f, "{--------------\n");
13967             PrintPosition(f, currentMove);
13968             fprintf(f, "--------------}\n\n");
13969
13970             SaveGame(f, 0, NULL); /* also closes the file*/
13971         }
13972
13973         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13974         nCmailMovesRegistered ++;
13975     } else if (nCmailGames == 1) {
13976         DisplayError(_("You have not made a move yet"), 0);
13977         return FALSE;
13978     }
13979
13980     return TRUE;
13981 }
13982
13983 void
13984 MailMoveEvent ()
13985 {
13986 #if !WIN32
13987     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13988     FILE *commandOutput;
13989     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13990     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13991     int nBuffers;
13992     int i;
13993     int archived;
13994     char *arcDir;
13995
13996     if (! cmailMsgLoaded) {
13997         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13998         return;
13999     }
14000
14001     if (nCmailGames == nCmailResults) {
14002         DisplayError(_("No unfinished games"), 0);
14003         return;
14004     }
14005
14006 #if CMAIL_PROHIBIT_REMAIL
14007     if (cmailMailedMove) {
14008       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);
14009         DisplayError(msg, 0);
14010         return;
14011     }
14012 #endif
14013
14014     if (! (cmailMailedMove || RegisterMove())) return;
14015
14016     if (   cmailMailedMove
14017         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14018       snprintf(string, MSG_SIZ, partCommandString,
14019                appData.debugMode ? " -v" : "", appData.cmailGameName);
14020         commandOutput = popen(string, "r");
14021
14022         if (commandOutput == NULL) {
14023             DisplayError(_("Failed to invoke cmail"), 0);
14024         } else {
14025             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14026                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14027             }
14028             if (nBuffers > 1) {
14029                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14030                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14031                 nBytes = MSG_SIZ - 1;
14032             } else {
14033                 (void) memcpy(msg, buffer, nBytes);
14034             }
14035             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14036
14037             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14038                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14039
14040                 archived = TRUE;
14041                 for (i = 0; i < nCmailGames; i ++) {
14042                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14043                         archived = FALSE;
14044                     }
14045                 }
14046                 if (   archived
14047                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14048                         != NULL)) {
14049                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14050                            arcDir,
14051                            appData.cmailGameName,
14052                            gameInfo.date);
14053                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14054                     cmailMsgLoaded = FALSE;
14055                 }
14056             }
14057
14058             DisplayInformation(msg);
14059             pclose(commandOutput);
14060         }
14061     } else {
14062         if ((*cmailMsg) != '\0') {
14063             DisplayInformation(cmailMsg);
14064         }
14065     }
14066
14067     return;
14068 #endif /* !WIN32 */
14069 }
14070
14071 char *
14072 CmailMsg ()
14073 {
14074 #if WIN32
14075     return NULL;
14076 #else
14077     int  prependComma = 0;
14078     char number[5];
14079     char string[MSG_SIZ];       /* Space for game-list */
14080     int  i;
14081
14082     if (!cmailMsgLoaded) return "";
14083
14084     if (cmailMailedMove) {
14085       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14086     } else {
14087         /* Create a list of games left */
14088       snprintf(string, MSG_SIZ, "[");
14089         for (i = 0; i < nCmailGames; i ++) {
14090             if (! (   cmailMoveRegistered[i]
14091                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14092                 if (prependComma) {
14093                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14094                 } else {
14095                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14096                     prependComma = 1;
14097                 }
14098
14099                 strcat(string, number);
14100             }
14101         }
14102         strcat(string, "]");
14103
14104         if (nCmailMovesRegistered + nCmailResults == 0) {
14105             switch (nCmailGames) {
14106               case 1:
14107                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14108                 break;
14109
14110               case 2:
14111                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14112                 break;
14113
14114               default:
14115                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14116                          nCmailGames);
14117                 break;
14118             }
14119         } else {
14120             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14121               case 1:
14122                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14123                          string);
14124                 break;
14125
14126               case 0:
14127                 if (nCmailResults == nCmailGames) {
14128                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14129                 } else {
14130                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14131                 }
14132                 break;
14133
14134               default:
14135                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14136                          string);
14137             }
14138         }
14139     }
14140     return cmailMsg;
14141 #endif /* WIN32 */
14142 }
14143
14144 void
14145 ResetGameEvent ()
14146 {
14147     if (gameMode == Training)
14148       SetTrainingModeOff();
14149
14150     Reset(TRUE, TRUE);
14151     cmailMsgLoaded = FALSE;
14152     if (appData.icsActive) {
14153       SendToICS(ics_prefix);
14154       SendToICS("refresh\n");
14155     }
14156 }
14157
14158 void
14159 ExitEvent (int status)
14160 {
14161     exiting++;
14162     if (exiting > 2) {
14163       /* Give up on clean exit */
14164       exit(status);
14165     }
14166     if (exiting > 1) {
14167       /* Keep trying for clean exit */
14168       return;
14169     }
14170
14171     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14172     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14173
14174     if (telnetISR != NULL) {
14175       RemoveInputSource(telnetISR);
14176     }
14177     if (icsPR != NoProc) {
14178       DestroyChildProcess(icsPR, TRUE);
14179     }
14180
14181     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14182     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14183
14184     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14185     /* make sure this other one finishes before killing it!                  */
14186     if(endingGame) { int count = 0;
14187         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14188         while(endingGame && count++ < 10) DoSleep(1);
14189         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14190     }
14191
14192     /* Kill off chess programs */
14193     if (first.pr != NoProc) {
14194         ExitAnalyzeMode();
14195
14196         DoSleep( appData.delayBeforeQuit );
14197         SendToProgram("quit\n", &first);
14198         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14199     }
14200     if (second.pr != NoProc) {
14201         DoSleep( appData.delayBeforeQuit );
14202         SendToProgram("quit\n", &second);
14203         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14204     }
14205     if (first.isr != NULL) {
14206         RemoveInputSource(first.isr);
14207     }
14208     if (second.isr != NULL) {
14209         RemoveInputSource(second.isr);
14210     }
14211
14212     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14213     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14214
14215     ShutDownFrontEnd();
14216     exit(status);
14217 }
14218
14219 void
14220 PauseEngine (ChessProgramState *cps)
14221 {
14222     SendToProgram("pause\n", cps);
14223     cps->pause = 2;
14224 }
14225
14226 void
14227 UnPauseEngine (ChessProgramState *cps)
14228 {
14229     SendToProgram("resume\n", cps);
14230     cps->pause = 1;
14231 }
14232
14233 void
14234 PauseEvent ()
14235 {
14236     if (appData.debugMode)
14237         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14238     if (pausing) {
14239         pausing = FALSE;
14240         ModeHighlight();
14241         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14242             StartClocks();
14243             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14244                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14245                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14246             }
14247             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14248             HandleMachineMove(stashedInputMove, stalledEngine);
14249             stalledEngine = NULL;
14250             return;
14251         }
14252         if (gameMode == MachinePlaysWhite ||
14253             gameMode == TwoMachinesPlay   ||
14254             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14255             if(first.pause)  UnPauseEngine(&first);
14256             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14257             if(second.pause) UnPauseEngine(&second);
14258             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14259             StartClocks();
14260         } else {
14261             DisplayBothClocks();
14262         }
14263         if (gameMode == PlayFromGameFile) {
14264             if (appData.timeDelay >= 0)
14265                 AutoPlayGameLoop();
14266         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14267             Reset(FALSE, TRUE);
14268             SendToICS(ics_prefix);
14269             SendToICS("refresh\n");
14270         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14271             ForwardInner(forwardMostMove);
14272         }
14273         pauseExamInvalid = FALSE;
14274     } else {
14275         switch (gameMode) {
14276           default:
14277             return;
14278           case IcsExamining:
14279             pauseExamForwardMostMove = forwardMostMove;
14280             pauseExamInvalid = FALSE;
14281             /* fall through */
14282           case IcsObserving:
14283           case IcsPlayingWhite:
14284           case IcsPlayingBlack:
14285             pausing = TRUE;
14286             ModeHighlight();
14287             return;
14288           case PlayFromGameFile:
14289             (void) StopLoadGameTimer();
14290             pausing = TRUE;
14291             ModeHighlight();
14292             break;
14293           case BeginningOfGame:
14294             if (appData.icsActive) return;
14295             /* else fall through */
14296           case MachinePlaysWhite:
14297           case MachinePlaysBlack:
14298           case TwoMachinesPlay:
14299             if (forwardMostMove == 0)
14300               return;           /* don't pause if no one has moved */
14301             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14302                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14303                 if(onMove->pause) {           // thinking engine can be paused
14304                     PauseEngine(onMove);      // do it
14305                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14306                         PauseEngine(onMove->other);
14307                     else
14308                         SendToProgram("easy\n", onMove->other);
14309                     StopClocks();
14310                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14311             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14312                 if(first.pause) {
14313                     PauseEngine(&first);
14314                     StopClocks();
14315                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14316             } else { // human on move, pause pondering by either method
14317                 if(first.pause)
14318                     PauseEngine(&first);
14319                 else if(appData.ponderNextMove)
14320                     SendToProgram("easy\n", &first);
14321                 StopClocks();
14322             }
14323             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14324           case AnalyzeMode:
14325             pausing = TRUE;
14326             ModeHighlight();
14327             break;
14328         }
14329     }
14330 }
14331
14332 void
14333 EditCommentEvent ()
14334 {
14335     char title[MSG_SIZ];
14336
14337     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14338       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14339     } else {
14340       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14341                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14342                parseList[currentMove - 1]);
14343     }
14344
14345     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14346 }
14347
14348
14349 void
14350 EditTagsEvent ()
14351 {
14352     char *tags = PGNTags(&gameInfo);
14353     bookUp = FALSE;
14354     EditTagsPopUp(tags, NULL);
14355     free(tags);
14356 }
14357
14358 void
14359 ToggleSecond ()
14360 {
14361   if(second.analyzing) {
14362     SendToProgram("exit\n", &second);
14363     second.analyzing = FALSE;
14364   } else {
14365     if (second.pr == NoProc) StartChessProgram(&second);
14366     InitChessProgram(&second, FALSE);
14367     FeedMovesToProgram(&second, currentMove);
14368
14369     SendToProgram("analyze\n", &second);
14370     second.analyzing = TRUE;
14371   }
14372 }
14373
14374 /* Toggle ShowThinking */
14375 void
14376 ToggleShowThinking()
14377 {
14378   appData.showThinking = !appData.showThinking;
14379   ShowThinkingEvent();
14380 }
14381
14382 int
14383 AnalyzeModeEvent ()
14384 {
14385     char buf[MSG_SIZ];
14386
14387     if (!first.analysisSupport) {
14388       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14389       DisplayError(buf, 0);
14390       return 0;
14391     }
14392     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14393     if (appData.icsActive) {
14394         if (gameMode != IcsObserving) {
14395           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14396             DisplayError(buf, 0);
14397             /* secure check */
14398             if (appData.icsEngineAnalyze) {
14399                 if (appData.debugMode)
14400                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14401                 ExitAnalyzeMode();
14402                 ModeHighlight();
14403             }
14404             return 0;
14405         }
14406         /* if enable, user wants to disable icsEngineAnalyze */
14407         if (appData.icsEngineAnalyze) {
14408                 ExitAnalyzeMode();
14409                 ModeHighlight();
14410                 return 0;
14411         }
14412         appData.icsEngineAnalyze = TRUE;
14413         if (appData.debugMode)
14414             fprintf(debugFP, "ICS engine analyze starting... \n");
14415     }
14416
14417     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14418     if (appData.noChessProgram || gameMode == AnalyzeMode)
14419       return 0;
14420
14421     if (gameMode != AnalyzeFile) {
14422         if (!appData.icsEngineAnalyze) {
14423                EditGameEvent();
14424                if (gameMode != EditGame) return 0;
14425         }
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     if (!appData.icsEngineAnalyze) {
14435         gameMode = AnalyzeMode;
14436         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14437     }
14438     pausing = FALSE;
14439     ModeHighlight();
14440     SetGameInfo();
14441
14442     StartAnalysisClock();
14443     GetTimeMark(&lastNodeCountTime);
14444     lastNodeCount = 0;
14445     return 1;
14446 }
14447
14448 void
14449 AnalyzeFileEvent ()
14450 {
14451     if (appData.noChessProgram || gameMode == AnalyzeFile)
14452       return;
14453
14454     if (!first.analysisSupport) {
14455       char buf[MSG_SIZ];
14456       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14457       DisplayError(buf, 0);
14458       return;
14459     }
14460
14461     if (gameMode != AnalyzeMode) {
14462         keepInfo = 1; // mere annotating should not alter PGN tags
14463         EditGameEvent();
14464         keepInfo = 0;
14465         if (gameMode != EditGame) return;
14466         if (!appData.showThinking) ToggleShowThinking();
14467         ResurrectChessProgram();
14468         SendToProgram("analyze\n", &first);
14469         first.analyzing = TRUE;
14470         /*first.maybeThinking = TRUE;*/
14471         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14472         EngineOutputPopUp();
14473     }
14474     gameMode = AnalyzeFile;
14475     pausing = FALSE;
14476     ModeHighlight();
14477
14478     StartAnalysisClock();
14479     GetTimeMark(&lastNodeCountTime);
14480     lastNodeCount = 0;
14481     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14482     AnalysisPeriodicEvent(1);
14483 }
14484
14485 void
14486 MachineWhiteEvent ()
14487 {
14488     char buf[MSG_SIZ];
14489     char *bookHit = NULL;
14490
14491     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14492       return;
14493
14494
14495     if (gameMode == PlayFromGameFile ||
14496         gameMode == TwoMachinesPlay  ||
14497         gameMode == Training         ||
14498         gameMode == AnalyzeMode      ||
14499         gameMode == EndOfGame)
14500         EditGameEvent();
14501
14502     if (gameMode == EditPosition)
14503         EditPositionDone(TRUE);
14504
14505     if (!WhiteOnMove(currentMove)) {
14506         DisplayError(_("It is not White's turn"), 0);
14507         return;
14508     }
14509
14510     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14511       ExitAnalyzeMode();
14512
14513     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14514         gameMode == AnalyzeFile)
14515         TruncateGame();
14516
14517     ResurrectChessProgram();    /* in case it isn't running */
14518     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14519         gameMode = MachinePlaysWhite;
14520         ResetClocks();
14521     } else
14522     gameMode = MachinePlaysWhite;
14523     pausing = FALSE;
14524     ModeHighlight();
14525     SetGameInfo();
14526     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14527     DisplayTitle(buf);
14528     if (first.sendName) {
14529       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14530       SendToProgram(buf, &first);
14531     }
14532     if (first.sendTime) {
14533       if (first.useColors) {
14534         SendToProgram("black\n", &first); /*gnu kludge*/
14535       }
14536       SendTimeRemaining(&first, TRUE);
14537     }
14538     if (first.useColors) {
14539       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14540     }
14541     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14542     SetMachineThinkingEnables();
14543     first.maybeThinking = TRUE;
14544     StartClocks();
14545     firstMove = FALSE;
14546
14547     if (appData.autoFlipView && !flipView) {
14548       flipView = !flipView;
14549       DrawPosition(FALSE, NULL);
14550       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14551     }
14552
14553     if(bookHit) { // [HGM] book: simulate book reply
14554         static char bookMove[MSG_SIZ]; // a bit generous?
14555
14556         programStats.nodes = programStats.depth = programStats.time =
14557         programStats.score = programStats.got_only_move = 0;
14558         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14559
14560         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14561         strcat(bookMove, bookHit);
14562         HandleMachineMove(bookMove, &first);
14563     }
14564 }
14565
14566 void
14567 MachineBlackEvent ()
14568 {
14569   char buf[MSG_SIZ];
14570   char *bookHit = NULL;
14571
14572     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14573         return;
14574
14575
14576     if (gameMode == PlayFromGameFile ||
14577         gameMode == TwoMachinesPlay  ||
14578         gameMode == Training         ||
14579         gameMode == AnalyzeMode      ||
14580         gameMode == EndOfGame)
14581         EditGameEvent();
14582
14583     if (gameMode == EditPosition)
14584         EditPositionDone(TRUE);
14585
14586     if (WhiteOnMove(currentMove)) {
14587         DisplayError(_("It is not Black's turn"), 0);
14588         return;
14589     }
14590
14591     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14592       ExitAnalyzeMode();
14593
14594     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14595         gameMode == AnalyzeFile)
14596         TruncateGame();
14597
14598     ResurrectChessProgram();    /* in case it isn't running */
14599     gameMode = MachinePlaysBlack;
14600     pausing = FALSE;
14601     ModeHighlight();
14602     SetGameInfo();
14603     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14604     DisplayTitle(buf);
14605     if (first.sendName) {
14606       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14607       SendToProgram(buf, &first);
14608     }
14609     if (first.sendTime) {
14610       if (first.useColors) {
14611         SendToProgram("white\n", &first); /*gnu kludge*/
14612       }
14613       SendTimeRemaining(&first, FALSE);
14614     }
14615     if (first.useColors) {
14616       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14617     }
14618     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14619     SetMachineThinkingEnables();
14620     first.maybeThinking = TRUE;
14621     StartClocks();
14622
14623     if (appData.autoFlipView && flipView) {
14624       flipView = !flipView;
14625       DrawPosition(FALSE, NULL);
14626       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14627     }
14628     if(bookHit) { // [HGM] book: simulate book reply
14629         static char bookMove[MSG_SIZ]; // a bit generous?
14630
14631         programStats.nodes = programStats.depth = programStats.time =
14632         programStats.score = programStats.got_only_move = 0;
14633         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14634
14635         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14636         strcat(bookMove, bookHit);
14637         HandleMachineMove(bookMove, &first);
14638     }
14639 }
14640
14641
14642 void
14643 DisplayTwoMachinesTitle ()
14644 {
14645     char buf[MSG_SIZ];
14646     if (appData.matchGames > 0) {
14647         if(appData.tourneyFile[0]) {
14648           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14649                    gameInfo.white, _("vs."), gameInfo.black,
14650                    nextGame+1, appData.matchGames+1,
14651                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14652         } else
14653         if (first.twoMachinesColor[0] == 'w') {
14654           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14655                    gameInfo.white, _("vs."),  gameInfo.black,
14656                    first.matchWins, second.matchWins,
14657                    matchGame - 1 - (first.matchWins + second.matchWins));
14658         } else {
14659           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14660                    gameInfo.white, _("vs."), gameInfo.black,
14661                    second.matchWins, first.matchWins,
14662                    matchGame - 1 - (first.matchWins + second.matchWins));
14663         }
14664     } else {
14665       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14666     }
14667     DisplayTitle(buf);
14668 }
14669
14670 void
14671 SettingsMenuIfReady ()
14672 {
14673   if (second.lastPing != second.lastPong) {
14674     DisplayMessage("", _("Waiting for second chess program"));
14675     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14676     return;
14677   }
14678   ThawUI();
14679   DisplayMessage("", "");
14680   SettingsPopUp(&second);
14681 }
14682
14683 int
14684 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14685 {
14686     char buf[MSG_SIZ];
14687     if (cps->pr == NoProc) {
14688         StartChessProgram(cps);
14689         if (cps->protocolVersion == 1) {
14690           retry();
14691           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14692         } else {
14693           /* kludge: allow timeout for initial "feature" command */
14694           if(retry != TwoMachinesEventIfReady) FreezeUI();
14695           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14696           DisplayMessage("", buf);
14697           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14698         }
14699         return 1;
14700     }
14701     return 0;
14702 }
14703
14704 void
14705 TwoMachinesEvent P((void))
14706 {
14707     int i;
14708     char buf[MSG_SIZ];
14709     ChessProgramState *onmove;
14710     char *bookHit = NULL;
14711     static int stalling = 0;
14712     TimeMark now;
14713     long wait;
14714
14715     if (appData.noChessProgram) return;
14716
14717     switch (gameMode) {
14718       case TwoMachinesPlay:
14719         return;
14720       case MachinePlaysWhite:
14721       case MachinePlaysBlack:
14722         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14723             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14724             return;
14725         }
14726         /* fall through */
14727       case BeginningOfGame:
14728       case PlayFromGameFile:
14729       case EndOfGame:
14730         EditGameEvent();
14731         if (gameMode != EditGame) return;
14732         break;
14733       case EditPosition:
14734         EditPositionDone(TRUE);
14735         break;
14736       case AnalyzeMode:
14737       case AnalyzeFile:
14738         ExitAnalyzeMode();
14739         break;
14740       case EditGame:
14741       default:
14742         break;
14743     }
14744
14745 //    forwardMostMove = currentMove;
14746     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14747     startingEngine = TRUE;
14748
14749     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14750
14751     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14752     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14753       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14754       return;
14755     }
14756     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14757
14758     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14759                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14760         startingEngine = matchMode = FALSE;
14761         DisplayError("second engine does not play this", 0);
14762         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14763         EditGameEvent(); // switch back to EditGame mode
14764         return;
14765     }
14766
14767     if(!stalling) {
14768       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14769       SendToProgram("force\n", &second);
14770       stalling = 1;
14771       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14772       return;
14773     }
14774     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14775     if(appData.matchPause>10000 || appData.matchPause<10)
14776                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14777     wait = SubtractTimeMarks(&now, &pauseStart);
14778     if(wait < appData.matchPause) {
14779         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14780         return;
14781     }
14782     // we are now committed to starting the game
14783     stalling = 0;
14784     DisplayMessage("", "");
14785     if (startedFromSetupPosition) {
14786         SendBoard(&second, backwardMostMove);
14787     if (appData.debugMode) {
14788         fprintf(debugFP, "Two Machines\n");
14789     }
14790     }
14791     for (i = backwardMostMove; i < forwardMostMove; i++) {
14792         SendMoveToProgram(i, &second);
14793     }
14794
14795     gameMode = TwoMachinesPlay;
14796     pausing = startingEngine = FALSE;
14797     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14798     SetGameInfo();
14799     DisplayTwoMachinesTitle();
14800     firstMove = TRUE;
14801     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14802         onmove = &first;
14803     } else {
14804         onmove = &second;
14805     }
14806     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14807     SendToProgram(first.computerString, &first);
14808     if (first.sendName) {
14809       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14810       SendToProgram(buf, &first);
14811     }
14812     SendToProgram(second.computerString, &second);
14813     if (second.sendName) {
14814       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14815       SendToProgram(buf, &second);
14816     }
14817
14818     ResetClocks();
14819     if (!first.sendTime || !second.sendTime) {
14820         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14821         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14822     }
14823     if (onmove->sendTime) {
14824       if (onmove->useColors) {
14825         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14826       }
14827       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14828     }
14829     if (onmove->useColors) {
14830       SendToProgram(onmove->twoMachinesColor, onmove);
14831     }
14832     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14833 //    SendToProgram("go\n", onmove);
14834     onmove->maybeThinking = TRUE;
14835     SetMachineThinkingEnables();
14836
14837     StartClocks();
14838
14839     if(bookHit) { // [HGM] book: simulate book reply
14840         static char bookMove[MSG_SIZ]; // a bit generous?
14841
14842         programStats.nodes = programStats.depth = programStats.time =
14843         programStats.score = programStats.got_only_move = 0;
14844         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14845
14846         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14847         strcat(bookMove, bookHit);
14848         savedMessage = bookMove; // args for deferred call
14849         savedState = onmove;
14850         ScheduleDelayedEvent(DeferredBookMove, 1);
14851     }
14852 }
14853
14854 void
14855 TrainingEvent ()
14856 {
14857     if (gameMode == Training) {
14858       SetTrainingModeOff();
14859       gameMode = PlayFromGameFile;
14860       DisplayMessage("", _("Training mode off"));
14861     } else {
14862       gameMode = Training;
14863       animateTraining = appData.animate;
14864
14865       /* make sure we are not already at the end of the game */
14866       if (currentMove < forwardMostMove) {
14867         SetTrainingModeOn();
14868         DisplayMessage("", _("Training mode on"));
14869       } else {
14870         gameMode = PlayFromGameFile;
14871         DisplayError(_("Already at end of game"), 0);
14872       }
14873     }
14874     ModeHighlight();
14875 }
14876
14877 void
14878 IcsClientEvent ()
14879 {
14880     if (!appData.icsActive) return;
14881     switch (gameMode) {
14882       case IcsPlayingWhite:
14883       case IcsPlayingBlack:
14884       case IcsObserving:
14885       case IcsIdle:
14886       case BeginningOfGame:
14887       case IcsExamining:
14888         return;
14889
14890       case EditGame:
14891         break;
14892
14893       case EditPosition:
14894         EditPositionDone(TRUE);
14895         break;
14896
14897       case AnalyzeMode:
14898       case AnalyzeFile:
14899         ExitAnalyzeMode();
14900         break;
14901
14902       default:
14903         EditGameEvent();
14904         break;
14905     }
14906
14907     gameMode = IcsIdle;
14908     ModeHighlight();
14909     return;
14910 }
14911
14912 void
14913 EditGameEvent ()
14914 {
14915     int i;
14916
14917     switch (gameMode) {
14918       case Training:
14919         SetTrainingModeOff();
14920         break;
14921       case MachinePlaysWhite:
14922       case MachinePlaysBlack:
14923       case BeginningOfGame:
14924         SendToProgram("force\n", &first);
14925         SetUserThinkingEnables();
14926         break;
14927       case PlayFromGameFile:
14928         (void) StopLoadGameTimer();
14929         if (gameFileFP != NULL) {
14930             gameFileFP = NULL;
14931         }
14932         break;
14933       case EditPosition:
14934         EditPositionDone(TRUE);
14935         break;
14936       case AnalyzeMode:
14937       case AnalyzeFile:
14938         ExitAnalyzeMode();
14939         SendToProgram("force\n", &first);
14940         break;
14941       case TwoMachinesPlay:
14942         GameEnds(EndOfFile, NULL, GE_PLAYER);
14943         ResurrectChessProgram();
14944         SetUserThinkingEnables();
14945         break;
14946       case EndOfGame:
14947         ResurrectChessProgram();
14948         break;
14949       case IcsPlayingBlack:
14950       case IcsPlayingWhite:
14951         DisplayError(_("Warning: You are still playing a game"), 0);
14952         break;
14953       case IcsObserving:
14954         DisplayError(_("Warning: You are still observing a game"), 0);
14955         break;
14956       case IcsExamining:
14957         DisplayError(_("Warning: You are still examining a game"), 0);
14958         break;
14959       case IcsIdle:
14960         break;
14961       case EditGame:
14962       default:
14963         return;
14964     }
14965
14966     pausing = FALSE;
14967     StopClocks();
14968     first.offeredDraw = second.offeredDraw = 0;
14969
14970     if (gameMode == PlayFromGameFile) {
14971         whiteTimeRemaining = timeRemaining[0][currentMove];
14972         blackTimeRemaining = timeRemaining[1][currentMove];
14973         DisplayTitle("");
14974     }
14975
14976     if (gameMode == MachinePlaysWhite ||
14977         gameMode == MachinePlaysBlack ||
14978         gameMode == TwoMachinesPlay ||
14979         gameMode == EndOfGame) {
14980         i = forwardMostMove;
14981         while (i > currentMove) {
14982             SendToProgram("undo\n", &first);
14983             i--;
14984         }
14985         if(!adjustedClock) {
14986         whiteTimeRemaining = timeRemaining[0][currentMove];
14987         blackTimeRemaining = timeRemaining[1][currentMove];
14988         DisplayBothClocks();
14989         }
14990         if (whiteFlag || blackFlag) {
14991             whiteFlag = blackFlag = 0;
14992         }
14993         DisplayTitle("");
14994     }
14995
14996     gameMode = EditGame;
14997     ModeHighlight();
14998     SetGameInfo();
14999 }
15000
15001
15002 void
15003 EditPositionEvent ()
15004 {
15005     if (gameMode == EditPosition) {
15006         EditGameEvent();
15007         return;
15008     }
15009
15010     EditGameEvent();
15011     if (gameMode != EditGame) return;
15012
15013     gameMode = EditPosition;
15014     ModeHighlight();
15015     SetGameInfo();
15016     if (currentMove > 0)
15017       CopyBoard(boards[0], boards[currentMove]);
15018
15019     blackPlaysFirst = !WhiteOnMove(currentMove);
15020     ResetClocks();
15021     currentMove = forwardMostMove = backwardMostMove = 0;
15022     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15023     DisplayMove(-1);
15024     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15025 }
15026
15027 void
15028 ExitAnalyzeMode ()
15029 {
15030     /* [DM] icsEngineAnalyze - possible call from other functions */
15031     if (appData.icsEngineAnalyze) {
15032         appData.icsEngineAnalyze = FALSE;
15033
15034         DisplayMessage("",_("Close ICS engine analyze..."));
15035     }
15036     if (first.analysisSupport && first.analyzing) {
15037       SendToBoth("exit\n");
15038       first.analyzing = second.analyzing = FALSE;
15039     }
15040     thinkOutput[0] = NULLCHAR;
15041 }
15042
15043 void
15044 EditPositionDone (Boolean fakeRights)
15045 {
15046     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15047
15048     startedFromSetupPosition = TRUE;
15049     InitChessProgram(&first, FALSE);
15050     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15051       boards[0][EP_STATUS] = EP_NONE;
15052       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15053       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15054         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15055         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15056       } else boards[0][CASTLING][2] = NoRights;
15057       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15058         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15059         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15060       } else boards[0][CASTLING][5] = NoRights;
15061       if(gameInfo.variant == VariantSChess) {
15062         int i;
15063         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15064           boards[0][VIRGIN][i] = 0;
15065           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15066           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15067         }
15068       }
15069     }
15070     SendToProgram("force\n", &first);
15071     if (blackPlaysFirst) {
15072         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15073         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15074         currentMove = forwardMostMove = backwardMostMove = 1;
15075         CopyBoard(boards[1], boards[0]);
15076     } else {
15077         currentMove = forwardMostMove = backwardMostMove = 0;
15078     }
15079     SendBoard(&first, forwardMostMove);
15080     if (appData.debugMode) {
15081         fprintf(debugFP, "EditPosDone\n");
15082     }
15083     DisplayTitle("");
15084     DisplayMessage("", "");
15085     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15086     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15087     gameMode = EditGame;
15088     ModeHighlight();
15089     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15090     ClearHighlights(); /* [AS] */
15091 }
15092
15093 /* Pause for `ms' milliseconds */
15094 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15095 void
15096 TimeDelay (long ms)
15097 {
15098     TimeMark m1, m2;
15099
15100     GetTimeMark(&m1);
15101     do {
15102         GetTimeMark(&m2);
15103     } while (SubtractTimeMarks(&m2, &m1) < ms);
15104 }
15105
15106 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15107 void
15108 SendMultiLineToICS (char *buf)
15109 {
15110     char temp[MSG_SIZ+1], *p;
15111     int len;
15112
15113     len = strlen(buf);
15114     if (len > MSG_SIZ)
15115       len = MSG_SIZ;
15116
15117     strncpy(temp, buf, len);
15118     temp[len] = 0;
15119
15120     p = temp;
15121     while (*p) {
15122         if (*p == '\n' || *p == '\r')
15123           *p = ' ';
15124         ++p;
15125     }
15126
15127     strcat(temp, "\n");
15128     SendToICS(temp);
15129     SendToPlayer(temp, strlen(temp));
15130 }
15131
15132 void
15133 SetWhiteToPlayEvent ()
15134 {
15135     if (gameMode == EditPosition) {
15136         blackPlaysFirst = FALSE;
15137         DisplayBothClocks();    /* works because currentMove is 0 */
15138     } else if (gameMode == IcsExamining) {
15139         SendToICS(ics_prefix);
15140         SendToICS("tomove white\n");
15141     }
15142 }
15143
15144 void
15145 SetBlackToPlayEvent ()
15146 {
15147     if (gameMode == EditPosition) {
15148         blackPlaysFirst = TRUE;
15149         currentMove = 1;        /* kludge */
15150         DisplayBothClocks();
15151         currentMove = 0;
15152     } else if (gameMode == IcsExamining) {
15153         SendToICS(ics_prefix);
15154         SendToICS("tomove black\n");
15155     }
15156 }
15157
15158 void
15159 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15160 {
15161     char buf[MSG_SIZ];
15162     ChessSquare piece = boards[0][y][x];
15163     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15164     static int lastVariant;
15165
15166     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15167
15168     switch (selection) {
15169       case ClearBoard:
15170         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15171         MarkTargetSquares(1);
15172         CopyBoard(currentBoard, boards[0]);
15173         CopyBoard(menuBoard, initialPosition);
15174         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15175             SendToICS(ics_prefix);
15176             SendToICS("bsetup clear\n");
15177         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15178             SendToICS(ics_prefix);
15179             SendToICS("clearboard\n");
15180         } else {
15181             int nonEmpty = 0;
15182             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15183                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15184                 for (y = 0; y < BOARD_HEIGHT; y++) {
15185                     if (gameMode == IcsExamining) {
15186                         if (boards[currentMove][y][x] != EmptySquare) {
15187                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15188                                     AAA + x, ONE + y);
15189                             SendToICS(buf);
15190                         }
15191                     } else if(boards[0][y][x] != DarkSquare) {
15192                         if(boards[0][y][x] != p) nonEmpty++;
15193                         boards[0][y][x] = p;
15194                     }
15195                 }
15196             }
15197             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15198                 int r;
15199                 for(r = 0; r < BOARD_HEIGHT; r++) {
15200                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15201                     ChessSquare p = menuBoard[r][x];
15202                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15203                   }
15204                 }
15205                 DisplayMessage("Clicking clock again restores position", "");
15206                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15207                 if(!nonEmpty) { // asked to clear an empty board
15208                     CopyBoard(boards[0], menuBoard);
15209                 } else
15210                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15211                     CopyBoard(boards[0], initialPosition);
15212                 } else
15213                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15214                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15215                     CopyBoard(boards[0], erasedBoard);
15216                 } else
15217                     CopyBoard(erasedBoard, currentBoard);
15218
15219             }
15220         }
15221         if (gameMode == EditPosition) {
15222             DrawPosition(FALSE, boards[0]);
15223         }
15224         break;
15225
15226       case WhitePlay:
15227         SetWhiteToPlayEvent();
15228         break;
15229
15230       case BlackPlay:
15231         SetBlackToPlayEvent();
15232         break;
15233
15234       case EmptySquare:
15235         if (gameMode == IcsExamining) {
15236             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15237             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15238             SendToICS(buf);
15239         } else {
15240             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15241                 if(x == BOARD_LEFT-2) {
15242                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15243                     boards[0][y][1] = 0;
15244                 } else
15245                 if(x == BOARD_RGHT+1) {
15246                     if(y >= gameInfo.holdingsSize) break;
15247                     boards[0][y][BOARD_WIDTH-2] = 0;
15248                 } else break;
15249             }
15250             boards[0][y][x] = EmptySquare;
15251             DrawPosition(FALSE, boards[0]);
15252         }
15253         break;
15254
15255       case PromotePiece:
15256         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15257            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15258             selection = (ChessSquare) (PROMOTED piece);
15259         } else if(piece == EmptySquare) selection = WhiteSilver;
15260         else selection = (ChessSquare)((int)piece - 1);
15261         goto defaultlabel;
15262
15263       case DemotePiece:
15264         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15265            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15266             selection = (ChessSquare) (DEMOTED piece);
15267         } else if(piece == EmptySquare) selection = BlackSilver;
15268         else selection = (ChessSquare)((int)piece + 1);
15269         goto defaultlabel;
15270
15271       case WhiteQueen:
15272       case BlackQueen:
15273         if(gameInfo.variant == VariantShatranj ||
15274            gameInfo.variant == VariantXiangqi  ||
15275            gameInfo.variant == VariantCourier  ||
15276            gameInfo.variant == VariantASEAN    ||
15277            gameInfo.variant == VariantMakruk     )
15278             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15279         goto defaultlabel;
15280
15281       case WhiteKing:
15282       case BlackKing:
15283         if(gameInfo.variant == VariantXiangqi)
15284             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15285         if(gameInfo.variant == VariantKnightmate)
15286             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15287       default:
15288         defaultlabel:
15289         if (gameMode == IcsExamining) {
15290             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15291             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15292                      PieceToChar(selection), AAA + x, ONE + y);
15293             SendToICS(buf);
15294         } else {
15295             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15296                 int n;
15297                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15298                     n = PieceToNumber(selection - BlackPawn);
15299                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15300                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15301                     boards[0][BOARD_HEIGHT-1-n][1]++;
15302                 } else
15303                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15304                     n = PieceToNumber(selection);
15305                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15306                     boards[0][n][BOARD_WIDTH-1] = selection;
15307                     boards[0][n][BOARD_WIDTH-2]++;
15308                 }
15309             } else
15310             boards[0][y][x] = selection;
15311             DrawPosition(TRUE, boards[0]);
15312             ClearHighlights();
15313             fromX = fromY = -1;
15314         }
15315         break;
15316     }
15317 }
15318
15319
15320 void
15321 DropMenuEvent (ChessSquare selection, int x, int y)
15322 {
15323     ChessMove moveType;
15324
15325     switch (gameMode) {
15326       case IcsPlayingWhite:
15327       case MachinePlaysBlack:
15328         if (!WhiteOnMove(currentMove)) {
15329             DisplayMoveError(_("It is Black's turn"));
15330             return;
15331         }
15332         moveType = WhiteDrop;
15333         break;
15334       case IcsPlayingBlack:
15335       case MachinePlaysWhite:
15336         if (WhiteOnMove(currentMove)) {
15337             DisplayMoveError(_("It is White's turn"));
15338             return;
15339         }
15340         moveType = BlackDrop;
15341         break;
15342       case EditGame:
15343         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15344         break;
15345       default:
15346         return;
15347     }
15348
15349     if (moveType == BlackDrop && selection < BlackPawn) {
15350       selection = (ChessSquare) ((int) selection
15351                                  + (int) BlackPawn - (int) WhitePawn);
15352     }
15353     if (boards[currentMove][y][x] != EmptySquare) {
15354         DisplayMoveError(_("That square is occupied"));
15355         return;
15356     }
15357
15358     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15359 }
15360
15361 void
15362 AcceptEvent ()
15363 {
15364     /* Accept a pending offer of any kind from opponent */
15365
15366     if (appData.icsActive) {
15367         SendToICS(ics_prefix);
15368         SendToICS("accept\n");
15369     } else if (cmailMsgLoaded) {
15370         if (currentMove == cmailOldMove &&
15371             commentList[cmailOldMove] != NULL &&
15372             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15373                    "Black offers a draw" : "White offers a draw")) {
15374             TruncateGame();
15375             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15376             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15377         } else {
15378             DisplayError(_("There is no pending offer on this move"), 0);
15379             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15380         }
15381     } else {
15382         /* Not used for offers from chess program */
15383     }
15384 }
15385
15386 void
15387 DeclineEvent ()
15388 {
15389     /* Decline a pending offer of any kind from opponent */
15390
15391     if (appData.icsActive) {
15392         SendToICS(ics_prefix);
15393         SendToICS("decline\n");
15394     } else if (cmailMsgLoaded) {
15395         if (currentMove == cmailOldMove &&
15396             commentList[cmailOldMove] != NULL &&
15397             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15398                    "Black offers a draw" : "White offers a draw")) {
15399 #ifdef NOTDEF
15400             AppendComment(cmailOldMove, "Draw declined", TRUE);
15401             DisplayComment(cmailOldMove - 1, "Draw declined");
15402 #endif /*NOTDEF*/
15403         } else {
15404             DisplayError(_("There is no pending offer on this move"), 0);
15405         }
15406     } else {
15407         /* Not used for offers from chess program */
15408     }
15409 }
15410
15411 void
15412 RematchEvent ()
15413 {
15414     /* Issue ICS rematch command */
15415     if (appData.icsActive) {
15416         SendToICS(ics_prefix);
15417         SendToICS("rematch\n");
15418     }
15419 }
15420
15421 void
15422 CallFlagEvent ()
15423 {
15424     /* Call your opponent's flag (claim a win on time) */
15425     if (appData.icsActive) {
15426         SendToICS(ics_prefix);
15427         SendToICS("flag\n");
15428     } else {
15429         switch (gameMode) {
15430           default:
15431             return;
15432           case MachinePlaysWhite:
15433             if (whiteFlag) {
15434                 if (blackFlag)
15435                   GameEnds(GameIsDrawn, "Both players ran out of time",
15436                            GE_PLAYER);
15437                 else
15438                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15439             } else {
15440                 DisplayError(_("Your opponent is not out of time"), 0);
15441             }
15442             break;
15443           case MachinePlaysBlack:
15444             if (blackFlag) {
15445                 if (whiteFlag)
15446                   GameEnds(GameIsDrawn, "Both players ran out of time",
15447                            GE_PLAYER);
15448                 else
15449                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15450             } else {
15451                 DisplayError(_("Your opponent is not out of time"), 0);
15452             }
15453             break;
15454         }
15455     }
15456 }
15457
15458 void
15459 ClockClick (int which)
15460 {       // [HGM] code moved to back-end from winboard.c
15461         if(which) { // black clock
15462           if (gameMode == EditPosition || gameMode == IcsExamining) {
15463             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15464             SetBlackToPlayEvent();
15465           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15466                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15467           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15468           } else if (shiftKey) {
15469             AdjustClock(which, -1);
15470           } else if (gameMode == IcsPlayingWhite ||
15471                      gameMode == MachinePlaysBlack) {
15472             CallFlagEvent();
15473           }
15474         } else { // white clock
15475           if (gameMode == EditPosition || gameMode == IcsExamining) {
15476             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15477             SetWhiteToPlayEvent();
15478           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15479                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15480           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15481           } else if (shiftKey) {
15482             AdjustClock(which, -1);
15483           } else if (gameMode == IcsPlayingBlack ||
15484                    gameMode == MachinePlaysWhite) {
15485             CallFlagEvent();
15486           }
15487         }
15488 }
15489
15490 void
15491 DrawEvent ()
15492 {
15493     /* Offer draw or accept pending draw offer from opponent */
15494
15495     if (appData.icsActive) {
15496         /* Note: tournament rules require draw offers to be
15497            made after you make your move but before you punch
15498            your clock.  Currently ICS doesn't let you do that;
15499            instead, you immediately punch your clock after making
15500            a move, but you can offer a draw at any time. */
15501
15502         SendToICS(ics_prefix);
15503         SendToICS("draw\n");
15504         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15505     } else if (cmailMsgLoaded) {
15506         if (currentMove == cmailOldMove &&
15507             commentList[cmailOldMove] != NULL &&
15508             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15509                    "Black offers a draw" : "White offers a draw")) {
15510             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15511             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15512         } else if (currentMove == cmailOldMove + 1) {
15513             char *offer = WhiteOnMove(cmailOldMove) ?
15514               "White offers a draw" : "Black offers a draw";
15515             AppendComment(currentMove, offer, TRUE);
15516             DisplayComment(currentMove - 1, offer);
15517             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15518         } else {
15519             DisplayError(_("You must make your move before offering a draw"), 0);
15520             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15521         }
15522     } else if (first.offeredDraw) {
15523         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15524     } else {
15525         if (first.sendDrawOffers) {
15526             SendToProgram("draw\n", &first);
15527             userOfferedDraw = TRUE;
15528         }
15529     }
15530 }
15531
15532 void
15533 AdjournEvent ()
15534 {
15535     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15536
15537     if (appData.icsActive) {
15538         SendToICS(ics_prefix);
15539         SendToICS("adjourn\n");
15540     } else {
15541         /* Currently GNU Chess doesn't offer or accept Adjourns */
15542     }
15543 }
15544
15545
15546 void
15547 AbortEvent ()
15548 {
15549     /* Offer Abort or accept pending Abort offer from opponent */
15550
15551     if (appData.icsActive) {
15552         SendToICS(ics_prefix);
15553         SendToICS("abort\n");
15554     } else {
15555         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15556     }
15557 }
15558
15559 void
15560 ResignEvent ()
15561 {
15562     /* Resign.  You can do this even if it's not your turn. */
15563
15564     if (appData.icsActive) {
15565         SendToICS(ics_prefix);
15566         SendToICS("resign\n");
15567     } else {
15568         switch (gameMode) {
15569           case MachinePlaysWhite:
15570             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15571             break;
15572           case MachinePlaysBlack:
15573             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15574             break;
15575           case EditGame:
15576             if (cmailMsgLoaded) {
15577                 TruncateGame();
15578                 if (WhiteOnMove(cmailOldMove)) {
15579                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15580                 } else {
15581                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15582                 }
15583                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15584             }
15585             break;
15586           default:
15587             break;
15588         }
15589     }
15590 }
15591
15592
15593 void
15594 StopObservingEvent ()
15595 {
15596     /* Stop observing current games */
15597     SendToICS(ics_prefix);
15598     SendToICS("unobserve\n");
15599 }
15600
15601 void
15602 StopExaminingEvent ()
15603 {
15604     /* Stop observing current game */
15605     SendToICS(ics_prefix);
15606     SendToICS("unexamine\n");
15607 }
15608
15609 void
15610 ForwardInner (int target)
15611 {
15612     int limit; int oldSeekGraphUp = seekGraphUp;
15613
15614     if (appData.debugMode)
15615         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15616                 target, currentMove, forwardMostMove);
15617
15618     if (gameMode == EditPosition)
15619       return;
15620
15621     seekGraphUp = FALSE;
15622     MarkTargetSquares(1);
15623     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15624
15625     if (gameMode == PlayFromGameFile && !pausing)
15626       PauseEvent();
15627
15628     if (gameMode == IcsExamining && pausing)
15629       limit = pauseExamForwardMostMove;
15630     else
15631       limit = forwardMostMove;
15632
15633     if (target > limit) target = limit;
15634
15635     if (target > 0 && moveList[target - 1][0]) {
15636         int fromX, fromY, toX, toY;
15637         toX = moveList[target - 1][2] - AAA;
15638         toY = moveList[target - 1][3] - ONE;
15639         if (moveList[target - 1][1] == '@') {
15640             if (appData.highlightLastMove) {
15641                 SetHighlights(-1, -1, toX, toY);
15642             }
15643         } else {
15644             int viaX = moveList[target - 1][5] - AAA;
15645             int viaY = moveList[target - 1][6] - ONE;
15646             fromX = moveList[target - 1][0] - AAA;
15647             fromY = moveList[target - 1][1] - ONE;
15648             if (target == currentMove + 1) {
15649                 if(moveList[target - 1][4] == ';') { // multi-leg
15650                     ChessSquare piece = boards[currentMove][viaY][viaX];
15651                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15652                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15653                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15654                     boards[currentMove][viaY][viaX] = piece;
15655                 } else
15656                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15657             }
15658             if (appData.highlightLastMove) {
15659                 SetHighlights(fromX, fromY, toX, toY);
15660             }
15661         }
15662     }
15663     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15664         gameMode == Training || gameMode == PlayFromGameFile ||
15665         gameMode == AnalyzeFile) {
15666         while (currentMove < target) {
15667             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15668             SendMoveToProgram(currentMove++, &first);
15669         }
15670     } else {
15671         currentMove = target;
15672     }
15673
15674     if (gameMode == EditGame || gameMode == EndOfGame) {
15675         whiteTimeRemaining = timeRemaining[0][currentMove];
15676         blackTimeRemaining = timeRemaining[1][currentMove];
15677     }
15678     DisplayBothClocks();
15679     DisplayMove(currentMove - 1);
15680     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15681     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15682     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15683         DisplayComment(currentMove - 1, commentList[currentMove]);
15684     }
15685     ClearMap(); // [HGM] exclude: invalidate map
15686 }
15687
15688
15689 void
15690 ForwardEvent ()
15691 {
15692     if (gameMode == IcsExamining && !pausing) {
15693         SendToICS(ics_prefix);
15694         SendToICS("forward\n");
15695     } else {
15696         ForwardInner(currentMove + 1);
15697     }
15698 }
15699
15700 void
15701 ToEndEvent ()
15702 {
15703     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15704         /* to optimze, we temporarily turn off analysis mode while we feed
15705          * the remaining moves to the engine. Otherwise we get analysis output
15706          * after each move.
15707          */
15708         if (first.analysisSupport) {
15709           SendToProgram("exit\nforce\n", &first);
15710           first.analyzing = FALSE;
15711         }
15712     }
15713
15714     if (gameMode == IcsExamining && !pausing) {
15715         SendToICS(ics_prefix);
15716         SendToICS("forward 999999\n");
15717     } else {
15718         ForwardInner(forwardMostMove);
15719     }
15720
15721     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15722         /* we have fed all the moves, so reactivate analysis mode */
15723         SendToProgram("analyze\n", &first);
15724         first.analyzing = TRUE;
15725         /*first.maybeThinking = TRUE;*/
15726         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15727     }
15728 }
15729
15730 void
15731 BackwardInner (int target)
15732 {
15733     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15734
15735     if (appData.debugMode)
15736         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15737                 target, currentMove, forwardMostMove);
15738
15739     if (gameMode == EditPosition) return;
15740     seekGraphUp = FALSE;
15741     MarkTargetSquares(1);
15742     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15743     if (currentMove <= backwardMostMove) {
15744         ClearHighlights();
15745         DrawPosition(full_redraw, boards[currentMove]);
15746         return;
15747     }
15748     if (gameMode == PlayFromGameFile && !pausing)
15749       PauseEvent();
15750
15751     if (moveList[target][0]) {
15752         int fromX, fromY, toX, toY;
15753         toX = moveList[target][2] - AAA;
15754         toY = moveList[target][3] - ONE;
15755         if (moveList[target][1] == '@') {
15756             if (appData.highlightLastMove) {
15757                 SetHighlights(-1, -1, toX, toY);
15758             }
15759         } else {
15760             fromX = moveList[target][0] - AAA;
15761             fromY = moveList[target][1] - ONE;
15762             if (target == currentMove - 1) {
15763                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15764             }
15765             if (appData.highlightLastMove) {
15766                 SetHighlights(fromX, fromY, toX, toY);
15767             }
15768         }
15769     }
15770     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15771         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15772         while (currentMove > target) {
15773             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15774                 // null move cannot be undone. Reload program with move history before it.
15775                 int i;
15776                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15777                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15778                 }
15779                 SendBoard(&first, i);
15780               if(second.analyzing) SendBoard(&second, i);
15781                 for(currentMove=i; currentMove<target; currentMove++) {
15782                     SendMoveToProgram(currentMove, &first);
15783                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15784                 }
15785                 break;
15786             }
15787             SendToBoth("undo\n");
15788             currentMove--;
15789         }
15790     } else {
15791         currentMove = target;
15792     }
15793
15794     if (gameMode == EditGame || gameMode == EndOfGame) {
15795         whiteTimeRemaining = timeRemaining[0][currentMove];
15796         blackTimeRemaining = timeRemaining[1][currentMove];
15797     }
15798     DisplayBothClocks();
15799     DisplayMove(currentMove - 1);
15800     DrawPosition(full_redraw, boards[currentMove]);
15801     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15802     // [HGM] PV info: routine tests if comment empty
15803     DisplayComment(currentMove - 1, commentList[currentMove]);
15804     ClearMap(); // [HGM] exclude: invalidate map
15805 }
15806
15807 void
15808 BackwardEvent ()
15809 {
15810     if (gameMode == IcsExamining && !pausing) {
15811         SendToICS(ics_prefix);
15812         SendToICS("backward\n");
15813     } else {
15814         BackwardInner(currentMove - 1);
15815     }
15816 }
15817
15818 void
15819 ToStartEvent ()
15820 {
15821     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15822         /* to optimize, we temporarily turn off analysis mode while we undo
15823          * all the moves. Otherwise we get analysis output after each undo.
15824          */
15825         if (first.analysisSupport) {
15826           SendToProgram("exit\nforce\n", &first);
15827           first.analyzing = FALSE;
15828         }
15829     }
15830
15831     if (gameMode == IcsExamining && !pausing) {
15832         SendToICS(ics_prefix);
15833         SendToICS("backward 999999\n");
15834     } else {
15835         BackwardInner(backwardMostMove);
15836     }
15837
15838     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15839         /* we have fed all the moves, so reactivate analysis mode */
15840         SendToProgram("analyze\n", &first);
15841         first.analyzing = TRUE;
15842         /*first.maybeThinking = TRUE;*/
15843         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15844     }
15845 }
15846
15847 void
15848 ToNrEvent (int to)
15849 {
15850   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15851   if (to >= forwardMostMove) to = forwardMostMove;
15852   if (to <= backwardMostMove) to = backwardMostMove;
15853   if (to < currentMove) {
15854     BackwardInner(to);
15855   } else {
15856     ForwardInner(to);
15857   }
15858 }
15859
15860 void
15861 RevertEvent (Boolean annotate)
15862 {
15863     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15864         return;
15865     }
15866     if (gameMode != IcsExamining) {
15867         DisplayError(_("You are not examining a game"), 0);
15868         return;
15869     }
15870     if (pausing) {
15871         DisplayError(_("You can't revert while pausing"), 0);
15872         return;
15873     }
15874     SendToICS(ics_prefix);
15875     SendToICS("revert\n");
15876 }
15877
15878 void
15879 RetractMoveEvent ()
15880 {
15881     switch (gameMode) {
15882       case MachinePlaysWhite:
15883       case MachinePlaysBlack:
15884         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15885             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15886             return;
15887         }
15888         if (forwardMostMove < 2) return;
15889         currentMove = forwardMostMove = forwardMostMove - 2;
15890         whiteTimeRemaining = timeRemaining[0][currentMove];
15891         blackTimeRemaining = timeRemaining[1][currentMove];
15892         DisplayBothClocks();
15893         DisplayMove(currentMove - 1);
15894         ClearHighlights();/*!! could figure this out*/
15895         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15896         SendToProgram("remove\n", &first);
15897         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15898         break;
15899
15900       case BeginningOfGame:
15901       default:
15902         break;
15903
15904       case IcsPlayingWhite:
15905       case IcsPlayingBlack:
15906         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15907             SendToICS(ics_prefix);
15908             SendToICS("takeback 2\n");
15909         } else {
15910             SendToICS(ics_prefix);
15911             SendToICS("takeback 1\n");
15912         }
15913         break;
15914     }
15915 }
15916
15917 void
15918 MoveNowEvent ()
15919 {
15920     ChessProgramState *cps;
15921
15922     switch (gameMode) {
15923       case MachinePlaysWhite:
15924         if (!WhiteOnMove(forwardMostMove)) {
15925             DisplayError(_("It is your turn"), 0);
15926             return;
15927         }
15928         cps = &first;
15929         break;
15930       case MachinePlaysBlack:
15931         if (WhiteOnMove(forwardMostMove)) {
15932             DisplayError(_("It is your turn"), 0);
15933             return;
15934         }
15935         cps = &first;
15936         break;
15937       case TwoMachinesPlay:
15938         if (WhiteOnMove(forwardMostMove) ==
15939             (first.twoMachinesColor[0] == 'w')) {
15940             cps = &first;
15941         } else {
15942             cps = &second;
15943         }
15944         break;
15945       case BeginningOfGame:
15946       default:
15947         return;
15948     }
15949     SendToProgram("?\n", cps);
15950 }
15951
15952 void
15953 TruncateGameEvent ()
15954 {
15955     EditGameEvent();
15956     if (gameMode != EditGame) return;
15957     TruncateGame();
15958 }
15959
15960 void
15961 TruncateGame ()
15962 {
15963     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15964     if (forwardMostMove > currentMove) {
15965         if (gameInfo.resultDetails != NULL) {
15966             free(gameInfo.resultDetails);
15967             gameInfo.resultDetails = NULL;
15968             gameInfo.result = GameUnfinished;
15969         }
15970         forwardMostMove = currentMove;
15971         HistorySet(parseList, backwardMostMove, forwardMostMove,
15972                    currentMove-1);
15973     }
15974 }
15975
15976 void
15977 HintEvent ()
15978 {
15979     if (appData.noChessProgram) return;
15980     switch (gameMode) {
15981       case MachinePlaysWhite:
15982         if (WhiteOnMove(forwardMostMove)) {
15983             DisplayError(_("Wait until your turn."), 0);
15984             return;
15985         }
15986         break;
15987       case BeginningOfGame:
15988       case MachinePlaysBlack:
15989         if (!WhiteOnMove(forwardMostMove)) {
15990             DisplayError(_("Wait until your turn."), 0);
15991             return;
15992         }
15993         break;
15994       default:
15995         DisplayError(_("No hint available"), 0);
15996         return;
15997     }
15998     SendToProgram("hint\n", &first);
15999     hintRequested = TRUE;
16000 }
16001
16002 int
16003 SaveSelected (FILE *g, int dummy, char *dummy2)
16004 {
16005     ListGame * lg = (ListGame *) gameList.head;
16006     int nItem, cnt=0;
16007     FILE *f;
16008
16009     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16010         DisplayError(_("Game list not loaded or empty"), 0);
16011         return 0;
16012     }
16013
16014     creatingBook = TRUE; // suppresses stuff during load game
16015
16016     /* Get list size */
16017     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16018         if(lg->position >= 0) { // selected?
16019             LoadGame(f, nItem, "", TRUE);
16020             SaveGamePGN2(g); // leaves g open
16021             cnt++; DoEvents();
16022         }
16023         lg = (ListGame *) lg->node.succ;
16024     }
16025
16026     fclose(g);
16027     creatingBook = FALSE;
16028
16029     return cnt;
16030 }
16031
16032 void
16033 CreateBookEvent ()
16034 {
16035     ListGame * lg = (ListGame *) gameList.head;
16036     FILE *f, *g;
16037     int nItem;
16038     static int secondTime = FALSE;
16039
16040     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16041         DisplayError(_("Game list not loaded or empty"), 0);
16042         return;
16043     }
16044
16045     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16046         fclose(g);
16047         secondTime++;
16048         DisplayNote(_("Book file exists! Try again for overwrite."));
16049         return;
16050     }
16051
16052     creatingBook = TRUE;
16053     secondTime = FALSE;
16054
16055     /* Get list size */
16056     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16057         if(lg->position >= 0) {
16058             LoadGame(f, nItem, "", TRUE);
16059             AddGameToBook(TRUE);
16060             DoEvents();
16061         }
16062         lg = (ListGame *) lg->node.succ;
16063     }
16064
16065     creatingBook = FALSE;
16066     FlushBook();
16067 }
16068
16069 void
16070 BookEvent ()
16071 {
16072     if (appData.noChessProgram) return;
16073     switch (gameMode) {
16074       case MachinePlaysWhite:
16075         if (WhiteOnMove(forwardMostMove)) {
16076             DisplayError(_("Wait until your turn."), 0);
16077             return;
16078         }
16079         break;
16080       case BeginningOfGame:
16081       case MachinePlaysBlack:
16082         if (!WhiteOnMove(forwardMostMove)) {
16083             DisplayError(_("Wait until your turn."), 0);
16084             return;
16085         }
16086         break;
16087       case EditPosition:
16088         EditPositionDone(TRUE);
16089         break;
16090       case TwoMachinesPlay:
16091         return;
16092       default:
16093         break;
16094     }
16095     SendToProgram("bk\n", &first);
16096     bookOutput[0] = NULLCHAR;
16097     bookRequested = TRUE;
16098 }
16099
16100 void
16101 AboutGameEvent ()
16102 {
16103     char *tags = PGNTags(&gameInfo);
16104     TagsPopUp(tags, CmailMsg());
16105     free(tags);
16106 }
16107
16108 /* end button procedures */
16109
16110 void
16111 PrintPosition (FILE *fp, int move)
16112 {
16113     int i, j;
16114
16115     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16116         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16117             char c = PieceToChar(boards[move][i][j]);
16118             fputc(c == 'x' ? '.' : c, fp);
16119             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16120         }
16121     }
16122     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16123       fprintf(fp, "white to play\n");
16124     else
16125       fprintf(fp, "black to play\n");
16126 }
16127
16128 void
16129 PrintOpponents (FILE *fp)
16130 {
16131     if (gameInfo.white != NULL) {
16132         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16133     } else {
16134         fprintf(fp, "\n");
16135     }
16136 }
16137
16138 /* Find last component of program's own name, using some heuristics */
16139 void
16140 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16141 {
16142     char *p, *q, c;
16143     int local = (strcmp(host, "localhost") == 0);
16144     while (!local && (p = strchr(prog, ';')) != NULL) {
16145         p++;
16146         while (*p == ' ') p++;
16147         prog = p;
16148     }
16149     if (*prog == '"' || *prog == '\'') {
16150         q = strchr(prog + 1, *prog);
16151     } else {
16152         q = strchr(prog, ' ');
16153     }
16154     if (q == NULL) q = prog + strlen(prog);
16155     p = q;
16156     while (p >= prog && *p != '/' && *p != '\\') p--;
16157     p++;
16158     if(p == prog && *p == '"') p++;
16159     c = *q; *q = 0;
16160     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16161     memcpy(buf, p, q - p);
16162     buf[q - p] = NULLCHAR;
16163     if (!local) {
16164         strcat(buf, "@");
16165         strcat(buf, host);
16166     }
16167 }
16168
16169 char *
16170 TimeControlTagValue ()
16171 {
16172     char buf[MSG_SIZ];
16173     if (!appData.clockMode) {
16174       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16175     } else if (movesPerSession > 0) {
16176       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16177     } else if (timeIncrement == 0) {
16178       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16179     } else {
16180       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16181     }
16182     return StrSave(buf);
16183 }
16184
16185 void
16186 SetGameInfo ()
16187 {
16188     /* This routine is used only for certain modes */
16189     VariantClass v = gameInfo.variant;
16190     ChessMove r = GameUnfinished;
16191     char *p = NULL;
16192
16193     if(keepInfo) return;
16194
16195     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16196         r = gameInfo.result;
16197         p = gameInfo.resultDetails;
16198         gameInfo.resultDetails = NULL;
16199     }
16200     ClearGameInfo(&gameInfo);
16201     gameInfo.variant = v;
16202
16203     switch (gameMode) {
16204       case MachinePlaysWhite:
16205         gameInfo.event = StrSave( appData.pgnEventHeader );
16206         gameInfo.site = StrSave(HostName());
16207         gameInfo.date = PGNDate();
16208         gameInfo.round = StrSave("-");
16209         gameInfo.white = StrSave(first.tidy);
16210         gameInfo.black = StrSave(UserName());
16211         gameInfo.timeControl = TimeControlTagValue();
16212         break;
16213
16214       case MachinePlaysBlack:
16215         gameInfo.event = StrSave( appData.pgnEventHeader );
16216         gameInfo.site = StrSave(HostName());
16217         gameInfo.date = PGNDate();
16218         gameInfo.round = StrSave("-");
16219         gameInfo.white = StrSave(UserName());
16220         gameInfo.black = StrSave(first.tidy);
16221         gameInfo.timeControl = TimeControlTagValue();
16222         break;
16223
16224       case TwoMachinesPlay:
16225         gameInfo.event = StrSave( appData.pgnEventHeader );
16226         gameInfo.site = StrSave(HostName());
16227         gameInfo.date = PGNDate();
16228         if (roundNr > 0) {
16229             char buf[MSG_SIZ];
16230             snprintf(buf, MSG_SIZ, "%d", roundNr);
16231             gameInfo.round = StrSave(buf);
16232         } else {
16233             gameInfo.round = StrSave("-");
16234         }
16235         if (first.twoMachinesColor[0] == 'w') {
16236             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16237             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16238         } else {
16239             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16240             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16241         }
16242         gameInfo.timeControl = TimeControlTagValue();
16243         break;
16244
16245       case EditGame:
16246         gameInfo.event = StrSave("Edited game");
16247         gameInfo.site = StrSave(HostName());
16248         gameInfo.date = PGNDate();
16249         gameInfo.round = StrSave("-");
16250         gameInfo.white = StrSave("-");
16251         gameInfo.black = StrSave("-");
16252         gameInfo.result = r;
16253         gameInfo.resultDetails = p;
16254         break;
16255
16256       case EditPosition:
16257         gameInfo.event = StrSave("Edited position");
16258         gameInfo.site = StrSave(HostName());
16259         gameInfo.date = PGNDate();
16260         gameInfo.round = StrSave("-");
16261         gameInfo.white = StrSave("-");
16262         gameInfo.black = StrSave("-");
16263         break;
16264
16265       case IcsPlayingWhite:
16266       case IcsPlayingBlack:
16267       case IcsObserving:
16268       case IcsExamining:
16269         break;
16270
16271       case PlayFromGameFile:
16272         gameInfo.event = StrSave("Game from non-PGN file");
16273         gameInfo.site = StrSave(HostName());
16274         gameInfo.date = PGNDate();
16275         gameInfo.round = StrSave("-");
16276         gameInfo.white = StrSave("?");
16277         gameInfo.black = StrSave("?");
16278         break;
16279
16280       default:
16281         break;
16282     }
16283 }
16284
16285 void
16286 ReplaceComment (int index, char *text)
16287 {
16288     int len;
16289     char *p;
16290     float score;
16291
16292     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16293        pvInfoList[index-1].depth == len &&
16294        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16295        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16296     while (*text == '\n') text++;
16297     len = strlen(text);
16298     while (len > 0 && text[len - 1] == '\n') len--;
16299
16300     if (commentList[index] != NULL)
16301       free(commentList[index]);
16302
16303     if (len == 0) {
16304         commentList[index] = NULL;
16305         return;
16306     }
16307   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16308       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16309       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16310     commentList[index] = (char *) malloc(len + 2);
16311     strncpy(commentList[index], text, len);
16312     commentList[index][len] = '\n';
16313     commentList[index][len + 1] = NULLCHAR;
16314   } else {
16315     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16316     char *p;
16317     commentList[index] = (char *) malloc(len + 7);
16318     safeStrCpy(commentList[index], "{\n", 3);
16319     safeStrCpy(commentList[index]+2, text, len+1);
16320     commentList[index][len+2] = NULLCHAR;
16321     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16322     strcat(commentList[index], "\n}\n");
16323   }
16324 }
16325
16326 void
16327 CrushCRs (char *text)
16328 {
16329   char *p = text;
16330   char *q = text;
16331   char ch;
16332
16333   do {
16334     ch = *p++;
16335     if (ch == '\r') continue;
16336     *q++ = ch;
16337   } while (ch != '\0');
16338 }
16339
16340 void
16341 AppendComment (int index, char *text, Boolean addBraces)
16342 /* addBraces  tells if we should add {} */
16343 {
16344     int oldlen, len;
16345     char *old;
16346
16347 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16348     if(addBraces == 3) addBraces = 0; else // force appending literally
16349     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16350
16351     CrushCRs(text);
16352     while (*text == '\n') text++;
16353     len = strlen(text);
16354     while (len > 0 && text[len - 1] == '\n') len--;
16355     text[len] = NULLCHAR;
16356
16357     if (len == 0) return;
16358
16359     if (commentList[index] != NULL) {
16360       Boolean addClosingBrace = addBraces;
16361         old = commentList[index];
16362         oldlen = strlen(old);
16363         while(commentList[index][oldlen-1] ==  '\n')
16364           commentList[index][--oldlen] = NULLCHAR;
16365         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16366         safeStrCpy(commentList[index], old, oldlen + len + 6);
16367         free(old);
16368         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16369         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16370           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16371           while (*text == '\n') { text++; len--; }
16372           commentList[index][--oldlen] = NULLCHAR;
16373       }
16374         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16375         else          strcat(commentList[index], "\n");
16376         strcat(commentList[index], text);
16377         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16378         else          strcat(commentList[index], "\n");
16379     } else {
16380         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16381         if(addBraces)
16382           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16383         else commentList[index][0] = NULLCHAR;
16384         strcat(commentList[index], text);
16385         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16386         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16387     }
16388 }
16389
16390 static char *
16391 FindStr (char * text, char * sub_text)
16392 {
16393     char * result = strstr( text, sub_text );
16394
16395     if( result != NULL ) {
16396         result += strlen( sub_text );
16397     }
16398
16399     return result;
16400 }
16401
16402 /* [AS] Try to extract PV info from PGN comment */
16403 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16404 char *
16405 GetInfoFromComment (int index, char * text)
16406 {
16407     char * sep = text, *p;
16408
16409     if( text != NULL && index > 0 ) {
16410         int score = 0;
16411         int depth = 0;
16412         int time = -1, sec = 0, deci;
16413         char * s_eval = FindStr( text, "[%eval " );
16414         char * s_emt = FindStr( text, "[%emt " );
16415 #if 0
16416         if( s_eval != NULL || s_emt != NULL ) {
16417 #else
16418         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16419 #endif
16420             /* New style */
16421             char delim;
16422
16423             if( s_eval != NULL ) {
16424                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16425                     return text;
16426                 }
16427
16428                 if( delim != ']' ) {
16429                     return text;
16430                 }
16431             }
16432
16433             if( s_emt != NULL ) {
16434             }
16435                 return text;
16436         }
16437         else {
16438             /* We expect something like: [+|-]nnn.nn/dd */
16439             int score_lo = 0;
16440
16441             if(*text != '{') return text; // [HGM] braces: must be normal comment
16442
16443             sep = strchr( text, '/' );
16444             if( sep == NULL || sep < (text+4) ) {
16445                 return text;
16446             }
16447
16448             p = text;
16449             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16450             if(p[1] == '(') { // comment starts with PV
16451                p = strchr(p, ')'); // locate end of PV
16452                if(p == NULL || sep < p+5) return text;
16453                // at this point we have something like "{(.*) +0.23/6 ..."
16454                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16455                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16456                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16457             }
16458             time = -1; sec = -1; deci = -1;
16459             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16460                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16461                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16462                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16463                 return text;
16464             }
16465
16466             if( score_lo < 0 || score_lo >= 100 ) {
16467                 return text;
16468             }
16469
16470             if(sec >= 0) time = 600*time + 10*sec; else
16471             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16472
16473             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16474
16475             /* [HGM] PV time: now locate end of PV info */
16476             while( *++sep >= '0' && *sep <= '9'); // strip depth
16477             if(time >= 0)
16478             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16479             if(sec >= 0)
16480             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16481             if(deci >= 0)
16482             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16483             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16484         }
16485
16486         if( depth <= 0 ) {
16487             return text;
16488         }
16489
16490         if( time < 0 ) {
16491             time = -1;
16492         }
16493
16494         pvInfoList[index-1].depth = depth;
16495         pvInfoList[index-1].score = score;
16496         pvInfoList[index-1].time  = 10*time; // centi-sec
16497         if(*sep == '}') *sep = 0; else *--sep = '{';
16498         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16499     }
16500     return sep;
16501 }
16502
16503 void
16504 SendToProgram (char *message, ChessProgramState *cps)
16505 {
16506     int count, outCount, error;
16507     char buf[MSG_SIZ];
16508
16509     if (cps->pr == NoProc) return;
16510     Attention(cps);
16511
16512     if (appData.debugMode) {
16513         TimeMark now;
16514         GetTimeMark(&now);
16515         fprintf(debugFP, "%ld >%-6s: %s",
16516                 SubtractTimeMarks(&now, &programStartTime),
16517                 cps->which, message);
16518         if(serverFP)
16519             fprintf(serverFP, "%ld >%-6s: %s",
16520                 SubtractTimeMarks(&now, &programStartTime),
16521                 cps->which, message), fflush(serverFP);
16522     }
16523
16524     count = strlen(message);
16525     outCount = OutputToProcess(cps->pr, message, count, &error);
16526     if (outCount < count && !exiting
16527                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16528       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16529       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16530         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16531             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16532                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16533                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16534                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16535             } else {
16536                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16537                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16538                 gameInfo.result = res;
16539             }
16540             gameInfo.resultDetails = StrSave(buf);
16541         }
16542         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16543         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16544     }
16545 }
16546
16547 void
16548 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16549 {
16550     char *end_str;
16551     char buf[MSG_SIZ];
16552     ChessProgramState *cps = (ChessProgramState *)closure;
16553
16554     if (isr != cps->isr) return; /* Killed intentionally */
16555     if (count <= 0) {
16556         if (count == 0) {
16557             RemoveInputSource(cps->isr);
16558             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16559                     _(cps->which), cps->program);
16560             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16561             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16562                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16563                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16564                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16565                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16566                 } else {
16567                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16568                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16569                     gameInfo.result = res;
16570                 }
16571                 gameInfo.resultDetails = StrSave(buf);
16572             }
16573             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16574             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16575         } else {
16576             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16577                     _(cps->which), cps->program);
16578             RemoveInputSource(cps->isr);
16579
16580             /* [AS] Program is misbehaving badly... kill it */
16581             if( count == -2 ) {
16582                 DestroyChildProcess( cps->pr, 9 );
16583                 cps->pr = NoProc;
16584             }
16585
16586             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16587         }
16588         return;
16589     }
16590
16591     if ((end_str = strchr(message, '\r')) != NULL)
16592       *end_str = NULLCHAR;
16593     if ((end_str = strchr(message, '\n')) != NULL)
16594       *end_str = NULLCHAR;
16595
16596     if (appData.debugMode) {
16597         TimeMark now; int print = 1;
16598         char *quote = ""; char c; int i;
16599
16600         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16601                 char start = message[0];
16602                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16603                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16604                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16605                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16606                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16607                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16608                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16609                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16610                    sscanf(message, "hint: %c", &c)!=1 &&
16611                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16612                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16613                     print = (appData.engineComments >= 2);
16614                 }
16615                 message[0] = start; // restore original message
16616         }
16617         if(print) {
16618                 GetTimeMark(&now);
16619                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16620                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16621                         quote,
16622                         message);
16623                 if(serverFP)
16624                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16625                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16626                         quote,
16627                         message), fflush(serverFP);
16628         }
16629     }
16630
16631     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16632     if (appData.icsEngineAnalyze) {
16633         if (strstr(message, "whisper") != NULL ||
16634              strstr(message, "kibitz") != NULL ||
16635             strstr(message, "tellics") != NULL) return;
16636     }
16637
16638     HandleMachineMove(message, cps);
16639 }
16640
16641
16642 void
16643 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16644 {
16645     char buf[MSG_SIZ];
16646     int seconds;
16647
16648     if( timeControl_2 > 0 ) {
16649         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16650             tc = timeControl_2;
16651         }
16652     }
16653     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16654     inc /= cps->timeOdds;
16655     st  /= cps->timeOdds;
16656
16657     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16658
16659     if (st > 0) {
16660       /* Set exact time per move, normally using st command */
16661       if (cps->stKludge) {
16662         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16663         seconds = st % 60;
16664         if (seconds == 0) {
16665           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16666         } else {
16667           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16668         }
16669       } else {
16670         snprintf(buf, MSG_SIZ, "st %d\n", st);
16671       }
16672     } else {
16673       /* Set conventional or incremental time control, using level command */
16674       if (seconds == 0) {
16675         /* Note old gnuchess bug -- minutes:seconds used to not work.
16676            Fixed in later versions, but still avoid :seconds
16677            when seconds is 0. */
16678         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16679       } else {
16680         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16681                  seconds, inc/1000.);
16682       }
16683     }
16684     SendToProgram(buf, cps);
16685
16686     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16687     /* Orthogonally, limit search to given depth */
16688     if (sd > 0) {
16689       if (cps->sdKludge) {
16690         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16691       } else {
16692         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16693       }
16694       SendToProgram(buf, cps);
16695     }
16696
16697     if(cps->nps >= 0) { /* [HGM] nps */
16698         if(cps->supportsNPS == FALSE)
16699           cps->nps = -1; // don't use if engine explicitly says not supported!
16700         else {
16701           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16702           SendToProgram(buf, cps);
16703         }
16704     }
16705 }
16706
16707 ChessProgramState *
16708 WhitePlayer ()
16709 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16710 {
16711     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16712        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16713         return &second;
16714     return &first;
16715 }
16716
16717 void
16718 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16719 {
16720     char message[MSG_SIZ];
16721     long time, otime;
16722
16723     /* Note: this routine must be called when the clocks are stopped
16724        or when they have *just* been set or switched; otherwise
16725        it will be off by the time since the current tick started.
16726     */
16727     if (machineWhite) {
16728         time = whiteTimeRemaining / 10;
16729         otime = blackTimeRemaining / 10;
16730     } else {
16731         time = blackTimeRemaining / 10;
16732         otime = whiteTimeRemaining / 10;
16733     }
16734     /* [HGM] translate opponent's time by time-odds factor */
16735     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16736
16737     if (time <= 0) time = 1;
16738     if (otime <= 0) otime = 1;
16739
16740     snprintf(message, MSG_SIZ, "time %ld\n", time);
16741     SendToProgram(message, cps);
16742
16743     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16744     SendToProgram(message, cps);
16745 }
16746
16747 char *
16748 EngineDefinedVariant (ChessProgramState *cps, int n)
16749 {   // return name of n-th unknown variant that engine supports
16750     static char buf[MSG_SIZ];
16751     char *p, *s = cps->variants;
16752     if(!s) return NULL;
16753     do { // parse string from variants feature
16754       VariantClass v;
16755         p = strchr(s, ',');
16756         if(p) *p = NULLCHAR;
16757       v = StringToVariant(s);
16758       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16759         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16760             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16761                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16762                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16763                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16764             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16765         }
16766         if(p) *p++ = ',';
16767         if(n < 0) return buf;
16768     } while(s = p);
16769     return NULL;
16770 }
16771
16772 int
16773 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16774 {
16775   char buf[MSG_SIZ];
16776   int len = strlen(name);
16777   int val;
16778
16779   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16780     (*p) += len + 1;
16781     sscanf(*p, "%d", &val);
16782     *loc = (val != 0);
16783     while (**p && **p != ' ')
16784       (*p)++;
16785     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16786     SendToProgram(buf, cps);
16787     return TRUE;
16788   }
16789   return FALSE;
16790 }
16791
16792 int
16793 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16794 {
16795   char buf[MSG_SIZ];
16796   int len = strlen(name);
16797   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16798     (*p) += len + 1;
16799     sscanf(*p, "%d", loc);
16800     while (**p && **p != ' ') (*p)++;
16801     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16802     SendToProgram(buf, cps);
16803     return TRUE;
16804   }
16805   return FALSE;
16806 }
16807
16808 int
16809 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16810 {
16811   char buf[MSG_SIZ];
16812   int len = strlen(name);
16813   if (strncmp((*p), name, len) == 0
16814       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16815     (*p) += len + 2;
16816     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16817     sscanf(*p, "%[^\"]", *loc);
16818     while (**p && **p != '\"') (*p)++;
16819     if (**p == '\"') (*p)++;
16820     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16821     SendToProgram(buf, cps);
16822     return TRUE;
16823   }
16824   return FALSE;
16825 }
16826
16827 int
16828 ParseOption (Option *opt, ChessProgramState *cps)
16829 // [HGM] options: process the string that defines an engine option, and determine
16830 // name, type, default value, and allowed value range
16831 {
16832         char *p, *q, buf[MSG_SIZ];
16833         int n, min = (-1)<<31, max = 1<<31, def;
16834
16835         if(p = strstr(opt->name, " -spin ")) {
16836             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16837             if(max < min) max = min; // enforce consistency
16838             if(def < min) def = min;
16839             if(def > max) def = max;
16840             opt->value = def;
16841             opt->min = min;
16842             opt->max = max;
16843             opt->type = Spin;
16844         } else if((p = strstr(opt->name, " -slider "))) {
16845             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16846             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16847             if(max < min) max = min; // enforce consistency
16848             if(def < min) def = min;
16849             if(def > max) def = max;
16850             opt->value = def;
16851             opt->min = min;
16852             opt->max = max;
16853             opt->type = Spin; // Slider;
16854         } else if((p = strstr(opt->name, " -string "))) {
16855             opt->textValue = p+9;
16856             opt->type = TextBox;
16857         } else if((p = strstr(opt->name, " -file "))) {
16858             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16859             opt->textValue = p+7;
16860             opt->type = FileName; // FileName;
16861         } else if((p = strstr(opt->name, " -path "))) {
16862             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16863             opt->textValue = p+7;
16864             opt->type = PathName; // PathName;
16865         } else if(p = strstr(opt->name, " -check ")) {
16866             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16867             opt->value = (def != 0);
16868             opt->type = CheckBox;
16869         } else if(p = strstr(opt->name, " -combo ")) {
16870             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16871             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16872             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16873             opt->value = n = 0;
16874             while(q = StrStr(q, " /// ")) {
16875                 n++; *q = 0;    // count choices, and null-terminate each of them
16876                 q += 5;
16877                 if(*q == '*') { // remember default, which is marked with * prefix
16878                     q++;
16879                     opt->value = n;
16880                 }
16881                 cps->comboList[cps->comboCnt++] = q;
16882             }
16883             cps->comboList[cps->comboCnt++] = NULL;
16884             opt->max = n + 1;
16885             opt->type = ComboBox;
16886         } else if(p = strstr(opt->name, " -button")) {
16887             opt->type = Button;
16888         } else if(p = strstr(opt->name, " -save")) {
16889             opt->type = SaveButton;
16890         } else return FALSE;
16891         *p = 0; // terminate option name
16892         // now look if the command-line options define a setting for this engine option.
16893         if(cps->optionSettings && cps->optionSettings[0])
16894             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16895         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16896           snprintf(buf, MSG_SIZ, "option %s", p);
16897                 if(p = strstr(buf, ",")) *p = 0;
16898                 if(q = strchr(buf, '=')) switch(opt->type) {
16899                     case ComboBox:
16900                         for(n=0; n<opt->max; n++)
16901                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16902                         break;
16903                     case TextBox:
16904                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16905                         break;
16906                     case Spin:
16907                     case CheckBox:
16908                         opt->value = atoi(q+1);
16909                     default:
16910                         break;
16911                 }
16912                 strcat(buf, "\n");
16913                 SendToProgram(buf, cps);
16914         }
16915         return TRUE;
16916 }
16917
16918 void
16919 FeatureDone (ChessProgramState *cps, int val)
16920 {
16921   DelayedEventCallback cb = GetDelayedEvent();
16922   if ((cb == InitBackEnd3 && cps == &first) ||
16923       (cb == SettingsMenuIfReady && cps == &second) ||
16924       (cb == LoadEngine) ||
16925       (cb == TwoMachinesEventIfReady)) {
16926     CancelDelayedEvent();
16927     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16928   }
16929   cps->initDone = val;
16930   if(val) cps->reload = FALSE;
16931 }
16932
16933 /* Parse feature command from engine */
16934 void
16935 ParseFeatures (char *args, ChessProgramState *cps)
16936 {
16937   char *p = args;
16938   char *q = NULL;
16939   int val;
16940   char buf[MSG_SIZ];
16941
16942   for (;;) {
16943     while (*p == ' ') p++;
16944     if (*p == NULLCHAR) return;
16945
16946     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16947     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16948     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16949     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16950     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16951     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16952     if (BoolFeature(&p, "reuse", &val, cps)) {
16953       /* Engine can disable reuse, but can't enable it if user said no */
16954       if (!val) cps->reuse = FALSE;
16955       continue;
16956     }
16957     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16958     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16959       if (gameMode == TwoMachinesPlay) {
16960         DisplayTwoMachinesTitle();
16961       } else {
16962         DisplayTitle("");
16963       }
16964       continue;
16965     }
16966     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16967     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16968     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16969     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16970     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16971     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16972     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16973     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16974     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16975     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16976     if (IntFeature(&p, "done", &val, cps)) {
16977       FeatureDone(cps, val);
16978       continue;
16979     }
16980     /* Added by Tord: */
16981     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16982     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16983     /* End of additions by Tord */
16984
16985     /* [HGM] added features: */
16986     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16987     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16988     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16989     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16990     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16991     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16992     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16993     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16994         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16995         FREE(cps->option[cps->nrOptions].name);
16996         cps->option[cps->nrOptions].name = q; q = NULL;
16997         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16998           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16999             SendToProgram(buf, cps);
17000             continue;
17001         }
17002         if(cps->nrOptions >= MAX_OPTIONS) {
17003             cps->nrOptions--;
17004             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17005             DisplayError(buf, 0);
17006         }
17007         continue;
17008     }
17009     /* End of additions by HGM */
17010
17011     /* unknown feature: complain and skip */
17012     q = p;
17013     while (*q && *q != '=') q++;
17014     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17015     SendToProgram(buf, cps);
17016     p = q;
17017     if (*p == '=') {
17018       p++;
17019       if (*p == '\"') {
17020         p++;
17021         while (*p && *p != '\"') p++;
17022         if (*p == '\"') p++;
17023       } else {
17024         while (*p && *p != ' ') p++;
17025       }
17026     }
17027   }
17028
17029 }
17030
17031 void
17032 PeriodicUpdatesEvent (int newState)
17033 {
17034     if (newState == appData.periodicUpdates)
17035       return;
17036
17037     appData.periodicUpdates=newState;
17038
17039     /* Display type changes, so update it now */
17040 //    DisplayAnalysis();
17041
17042     /* Get the ball rolling again... */
17043     if (newState) {
17044         AnalysisPeriodicEvent(1);
17045         StartAnalysisClock();
17046     }
17047 }
17048
17049 void
17050 PonderNextMoveEvent (int newState)
17051 {
17052     if (newState == appData.ponderNextMove) return;
17053     if (gameMode == EditPosition) EditPositionDone(TRUE);
17054     if (newState) {
17055         SendToProgram("hard\n", &first);
17056         if (gameMode == TwoMachinesPlay) {
17057             SendToProgram("hard\n", &second);
17058         }
17059     } else {
17060         SendToProgram("easy\n", &first);
17061         thinkOutput[0] = NULLCHAR;
17062         if (gameMode == TwoMachinesPlay) {
17063             SendToProgram("easy\n", &second);
17064         }
17065     }
17066     appData.ponderNextMove = newState;
17067 }
17068
17069 void
17070 NewSettingEvent (int option, int *feature, char *command, int value)
17071 {
17072     char buf[MSG_SIZ];
17073
17074     if (gameMode == EditPosition) EditPositionDone(TRUE);
17075     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17076     if(feature == NULL || *feature) SendToProgram(buf, &first);
17077     if (gameMode == TwoMachinesPlay) {
17078         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17079     }
17080 }
17081
17082 void
17083 ShowThinkingEvent ()
17084 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17085 {
17086     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17087     int newState = appData.showThinking
17088         // [HGM] thinking: other features now need thinking output as well
17089         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17090
17091     if (oldState == newState) return;
17092     oldState = newState;
17093     if (gameMode == EditPosition) EditPositionDone(TRUE);
17094     if (oldState) {
17095         SendToProgram("post\n", &first);
17096         if (gameMode == TwoMachinesPlay) {
17097             SendToProgram("post\n", &second);
17098         }
17099     } else {
17100         SendToProgram("nopost\n", &first);
17101         thinkOutput[0] = NULLCHAR;
17102         if (gameMode == TwoMachinesPlay) {
17103             SendToProgram("nopost\n", &second);
17104         }
17105     }
17106 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17107 }
17108
17109 void
17110 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17111 {
17112   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17113   if (pr == NoProc) return;
17114   AskQuestion(title, question, replyPrefix, pr);
17115 }
17116
17117 void
17118 TypeInEvent (char firstChar)
17119 {
17120     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17121         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17122         gameMode == AnalyzeMode || gameMode == EditGame ||
17123         gameMode == EditPosition || gameMode == IcsExamining ||
17124         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17125         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17126                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17127                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17128         gameMode == Training) PopUpMoveDialog(firstChar);
17129 }
17130
17131 void
17132 TypeInDoneEvent (char *move)
17133 {
17134         Board board;
17135         int n, fromX, fromY, toX, toY;
17136         char promoChar;
17137         ChessMove moveType;
17138
17139         // [HGM] FENedit
17140         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17141                 EditPositionPasteFEN(move);
17142                 return;
17143         }
17144         // [HGM] movenum: allow move number to be typed in any mode
17145         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17146           ToNrEvent(2*n-1);
17147           return;
17148         }
17149         // undocumented kludge: allow command-line option to be typed in!
17150         // (potentially fatal, and does not implement the effect of the option.)
17151         // should only be used for options that are values on which future decisions will be made,
17152         // and definitely not on options that would be used during initialization.
17153         if(strstr(move, "!!! -") == move) {
17154             ParseArgsFromString(move+4);
17155             return;
17156         }
17157
17158       if (gameMode != EditGame && currentMove != forwardMostMove &&
17159         gameMode != Training) {
17160         DisplayMoveError(_("Displayed move is not current"));
17161       } else {
17162         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17163           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17164         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17165         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17166           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17167           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17168         } else {
17169           DisplayMoveError(_("Could not parse move"));
17170         }
17171       }
17172 }
17173
17174 void
17175 DisplayMove (int moveNumber)
17176 {
17177     char message[MSG_SIZ];
17178     char res[MSG_SIZ];
17179     char cpThinkOutput[MSG_SIZ];
17180
17181     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17182
17183     if (moveNumber == forwardMostMove - 1 ||
17184         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17185
17186         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17187
17188         if (strchr(cpThinkOutput, '\n')) {
17189             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17190         }
17191     } else {
17192         *cpThinkOutput = NULLCHAR;
17193     }
17194
17195     /* [AS] Hide thinking from human user */
17196     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17197         *cpThinkOutput = NULLCHAR;
17198         if( thinkOutput[0] != NULLCHAR ) {
17199             int i;
17200
17201             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17202                 cpThinkOutput[i] = '.';
17203             }
17204             cpThinkOutput[i] = NULLCHAR;
17205             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17206         }
17207     }
17208
17209     if (moveNumber == forwardMostMove - 1 &&
17210         gameInfo.resultDetails != NULL) {
17211         if (gameInfo.resultDetails[0] == NULLCHAR) {
17212           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17213         } else {
17214           snprintf(res, MSG_SIZ, " {%s} %s",
17215                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17216         }
17217     } else {
17218         res[0] = NULLCHAR;
17219     }
17220
17221     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17222         DisplayMessage(res, cpThinkOutput);
17223     } else {
17224       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17225                 WhiteOnMove(moveNumber) ? " " : ".. ",
17226                 parseList[moveNumber], res);
17227         DisplayMessage(message, cpThinkOutput);
17228     }
17229 }
17230
17231 void
17232 DisplayComment (int moveNumber, char *text)
17233 {
17234     char title[MSG_SIZ];
17235
17236     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17237       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17238     } else {
17239       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17240               WhiteOnMove(moveNumber) ? " " : ".. ",
17241               parseList[moveNumber]);
17242     }
17243     if (text != NULL && (appData.autoDisplayComment || commentUp))
17244         CommentPopUp(title, text);
17245 }
17246
17247 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17248  * might be busy thinking or pondering.  It can be omitted if your
17249  * gnuchess is configured to stop thinking immediately on any user
17250  * input.  However, that gnuchess feature depends on the FIONREAD
17251  * ioctl, which does not work properly on some flavors of Unix.
17252  */
17253 void
17254 Attention (ChessProgramState *cps)
17255 {
17256 #if ATTENTION
17257     if (!cps->useSigint) return;
17258     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17259     switch (gameMode) {
17260       case MachinePlaysWhite:
17261       case MachinePlaysBlack:
17262       case TwoMachinesPlay:
17263       case IcsPlayingWhite:
17264       case IcsPlayingBlack:
17265       case AnalyzeMode:
17266       case AnalyzeFile:
17267         /* Skip if we know it isn't thinking */
17268         if (!cps->maybeThinking) return;
17269         if (appData.debugMode)
17270           fprintf(debugFP, "Interrupting %s\n", cps->which);
17271         InterruptChildProcess(cps->pr);
17272         cps->maybeThinking = FALSE;
17273         break;
17274       default:
17275         break;
17276     }
17277 #endif /*ATTENTION*/
17278 }
17279
17280 int
17281 CheckFlags ()
17282 {
17283     if (whiteTimeRemaining <= 0) {
17284         if (!whiteFlag) {
17285             whiteFlag = TRUE;
17286             if (appData.icsActive) {
17287                 if (appData.autoCallFlag &&
17288                     gameMode == IcsPlayingBlack && !blackFlag) {
17289                   SendToICS(ics_prefix);
17290                   SendToICS("flag\n");
17291                 }
17292             } else {
17293                 if (blackFlag) {
17294                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17295                 } else {
17296                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17297                     if (appData.autoCallFlag) {
17298                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17299                         return TRUE;
17300                     }
17301                 }
17302             }
17303         }
17304     }
17305     if (blackTimeRemaining <= 0) {
17306         if (!blackFlag) {
17307             blackFlag = TRUE;
17308             if (appData.icsActive) {
17309                 if (appData.autoCallFlag &&
17310                     gameMode == IcsPlayingWhite && !whiteFlag) {
17311                   SendToICS(ics_prefix);
17312                   SendToICS("flag\n");
17313                 }
17314             } else {
17315                 if (whiteFlag) {
17316                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17317                 } else {
17318                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17319                     if (appData.autoCallFlag) {
17320                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17321                         return TRUE;
17322                     }
17323                 }
17324             }
17325         }
17326     }
17327     return FALSE;
17328 }
17329
17330 void
17331 CheckTimeControl ()
17332 {
17333     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17334         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17335
17336     /*
17337      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17338      */
17339     if ( !WhiteOnMove(forwardMostMove) ) {
17340         /* White made time control */
17341         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17342         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17343         /* [HGM] time odds: correct new time quota for time odds! */
17344                                             / WhitePlayer()->timeOdds;
17345         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17346     } else {
17347         lastBlack -= blackTimeRemaining;
17348         /* Black made time control */
17349         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17350                                             / WhitePlayer()->other->timeOdds;
17351         lastWhite = whiteTimeRemaining;
17352     }
17353 }
17354
17355 void
17356 DisplayBothClocks ()
17357 {
17358     int wom = gameMode == EditPosition ?
17359       !blackPlaysFirst : WhiteOnMove(currentMove);
17360     DisplayWhiteClock(whiteTimeRemaining, wom);
17361     DisplayBlackClock(blackTimeRemaining, !wom);
17362 }
17363
17364
17365 /* Timekeeping seems to be a portability nightmare.  I think everyone
17366    has ftime(), but I'm really not sure, so I'm including some ifdefs
17367    to use other calls if you don't.  Clocks will be less accurate if
17368    you have neither ftime nor gettimeofday.
17369 */
17370
17371 /* VS 2008 requires the #include outside of the function */
17372 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17373 #include <sys/timeb.h>
17374 #endif
17375
17376 /* Get the current time as a TimeMark */
17377 void
17378 GetTimeMark (TimeMark *tm)
17379 {
17380 #if HAVE_GETTIMEOFDAY
17381
17382     struct timeval timeVal;
17383     struct timezone timeZone;
17384
17385     gettimeofday(&timeVal, &timeZone);
17386     tm->sec = (long) timeVal.tv_sec;
17387     tm->ms = (int) (timeVal.tv_usec / 1000L);
17388
17389 #else /*!HAVE_GETTIMEOFDAY*/
17390 #if HAVE_FTIME
17391
17392 // include <sys/timeb.h> / moved to just above start of function
17393     struct timeb timeB;
17394
17395     ftime(&timeB);
17396     tm->sec = (long) timeB.time;
17397     tm->ms = (int) timeB.millitm;
17398
17399 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17400     tm->sec = (long) time(NULL);
17401     tm->ms = 0;
17402 #endif
17403 #endif
17404 }
17405
17406 /* Return the difference in milliseconds between two
17407    time marks.  We assume the difference will fit in a long!
17408 */
17409 long
17410 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17411 {
17412     return 1000L*(tm2->sec - tm1->sec) +
17413            (long) (tm2->ms - tm1->ms);
17414 }
17415
17416
17417 /*
17418  * Code to manage the game clocks.
17419  *
17420  * In tournament play, black starts the clock and then white makes a move.
17421  * We give the human user a slight advantage if he is playing white---the
17422  * clocks don't run until he makes his first move, so it takes zero time.
17423  * Also, we don't account for network lag, so we could get out of sync
17424  * with GNU Chess's clock -- but then, referees are always right.
17425  */
17426
17427 static TimeMark tickStartTM;
17428 static long intendedTickLength;
17429
17430 long
17431 NextTickLength (long timeRemaining)
17432 {
17433     long nominalTickLength, nextTickLength;
17434
17435     if (timeRemaining > 0L && timeRemaining <= 10000L)
17436       nominalTickLength = 100L;
17437     else
17438       nominalTickLength = 1000L;
17439     nextTickLength = timeRemaining % nominalTickLength;
17440     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17441
17442     return nextTickLength;
17443 }
17444
17445 /* Adjust clock one minute up or down */
17446 void
17447 AdjustClock (Boolean which, int dir)
17448 {
17449     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17450     if(which) blackTimeRemaining += 60000*dir;
17451     else      whiteTimeRemaining += 60000*dir;
17452     DisplayBothClocks();
17453     adjustedClock = TRUE;
17454 }
17455
17456 /* Stop clocks and reset to a fresh time control */
17457 void
17458 ResetClocks ()
17459 {
17460     (void) StopClockTimer();
17461     if (appData.icsActive) {
17462         whiteTimeRemaining = blackTimeRemaining = 0;
17463     } else if (searchTime) {
17464         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17465         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17466     } else { /* [HGM] correct new time quote for time odds */
17467         whiteTC = blackTC = fullTimeControlString;
17468         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17469         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17470     }
17471     if (whiteFlag || blackFlag) {
17472         DisplayTitle("");
17473         whiteFlag = blackFlag = FALSE;
17474     }
17475     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17476     DisplayBothClocks();
17477     adjustedClock = FALSE;
17478 }
17479
17480 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17481
17482 /* Decrement running clock by amount of time that has passed */
17483 void
17484 DecrementClocks ()
17485 {
17486     long timeRemaining;
17487     long lastTickLength, fudge;
17488     TimeMark now;
17489
17490     if (!appData.clockMode) return;
17491     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17492
17493     GetTimeMark(&now);
17494
17495     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17496
17497     /* Fudge if we woke up a little too soon */
17498     fudge = intendedTickLength - lastTickLength;
17499     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17500
17501     if (WhiteOnMove(forwardMostMove)) {
17502         if(whiteNPS >= 0) lastTickLength = 0;
17503         timeRemaining = whiteTimeRemaining -= lastTickLength;
17504         if(timeRemaining < 0 && !appData.icsActive) {
17505             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17506             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17507                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17508                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17509             }
17510         }
17511         DisplayWhiteClock(whiteTimeRemaining - fudge,
17512                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17513     } else {
17514         if(blackNPS >= 0) lastTickLength = 0;
17515         timeRemaining = blackTimeRemaining -= lastTickLength;
17516         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17517             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17518             if(suddenDeath) {
17519                 blackStartMove = forwardMostMove;
17520                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17521             }
17522         }
17523         DisplayBlackClock(blackTimeRemaining - fudge,
17524                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17525     }
17526     if (CheckFlags()) return;
17527
17528     if(twoBoards) { // count down secondary board's clocks as well
17529         activePartnerTime -= lastTickLength;
17530         partnerUp = 1;
17531         if(activePartner == 'W')
17532             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17533         else
17534             DisplayBlackClock(activePartnerTime, TRUE);
17535         partnerUp = 0;
17536     }
17537
17538     tickStartTM = now;
17539     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17540     StartClockTimer(intendedTickLength);
17541
17542     /* if the time remaining has fallen below the alarm threshold, sound the
17543      * alarm. if the alarm has sounded and (due to a takeback or time control
17544      * with increment) the time remaining has increased to a level above the
17545      * threshold, reset the alarm so it can sound again.
17546      */
17547
17548     if (appData.icsActive && appData.icsAlarm) {
17549
17550         /* make sure we are dealing with the user's clock */
17551         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17552                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17553            )) return;
17554
17555         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17556             alarmSounded = FALSE;
17557         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17558             PlayAlarmSound();
17559             alarmSounded = TRUE;
17560         }
17561     }
17562 }
17563
17564
17565 /* A player has just moved, so stop the previously running
17566    clock and (if in clock mode) start the other one.
17567    We redisplay both clocks in case we're in ICS mode, because
17568    ICS gives us an update to both clocks after every move.
17569    Note that this routine is called *after* forwardMostMove
17570    is updated, so the last fractional tick must be subtracted
17571    from the color that is *not* on move now.
17572 */
17573 void
17574 SwitchClocks (int newMoveNr)
17575 {
17576     long lastTickLength;
17577     TimeMark now;
17578     int flagged = FALSE;
17579
17580     GetTimeMark(&now);
17581
17582     if (StopClockTimer() && appData.clockMode) {
17583         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17584         if (!WhiteOnMove(forwardMostMove)) {
17585             if(blackNPS >= 0) lastTickLength = 0;
17586             blackTimeRemaining -= lastTickLength;
17587            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17588 //         if(pvInfoList[forwardMostMove].time == -1)
17589                  pvInfoList[forwardMostMove].time =               // use GUI time
17590                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17591         } else {
17592            if(whiteNPS >= 0) lastTickLength = 0;
17593            whiteTimeRemaining -= lastTickLength;
17594            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17595 //         if(pvInfoList[forwardMostMove].time == -1)
17596                  pvInfoList[forwardMostMove].time =
17597                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17598         }
17599         flagged = CheckFlags();
17600     }
17601     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17602     CheckTimeControl();
17603
17604     if (flagged || !appData.clockMode) return;
17605
17606     switch (gameMode) {
17607       case MachinePlaysBlack:
17608       case MachinePlaysWhite:
17609       case BeginningOfGame:
17610         if (pausing) return;
17611         break;
17612
17613       case EditGame:
17614       case PlayFromGameFile:
17615       case IcsExamining:
17616         return;
17617
17618       default:
17619         break;
17620     }
17621
17622     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17623         if(WhiteOnMove(forwardMostMove))
17624              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17625         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17626     }
17627
17628     tickStartTM = now;
17629     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17630       whiteTimeRemaining : blackTimeRemaining);
17631     StartClockTimer(intendedTickLength);
17632 }
17633
17634
17635 /* Stop both clocks */
17636 void
17637 StopClocks ()
17638 {
17639     long lastTickLength;
17640     TimeMark now;
17641
17642     if (!StopClockTimer()) return;
17643     if (!appData.clockMode) return;
17644
17645     GetTimeMark(&now);
17646
17647     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17648     if (WhiteOnMove(forwardMostMove)) {
17649         if(whiteNPS >= 0) lastTickLength = 0;
17650         whiteTimeRemaining -= lastTickLength;
17651         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17652     } else {
17653         if(blackNPS >= 0) lastTickLength = 0;
17654         blackTimeRemaining -= lastTickLength;
17655         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17656     }
17657     CheckFlags();
17658 }
17659
17660 /* Start clock of player on move.  Time may have been reset, so
17661    if clock is already running, stop and restart it. */
17662 void
17663 StartClocks ()
17664 {
17665     (void) StopClockTimer(); /* in case it was running already */
17666     DisplayBothClocks();
17667     if (CheckFlags()) return;
17668
17669     if (!appData.clockMode) return;
17670     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17671
17672     GetTimeMark(&tickStartTM);
17673     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17674       whiteTimeRemaining : blackTimeRemaining);
17675
17676    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17677     whiteNPS = blackNPS = -1;
17678     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17679        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17680         whiteNPS = first.nps;
17681     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17682        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17683         blackNPS = first.nps;
17684     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17685         whiteNPS = second.nps;
17686     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17687         blackNPS = second.nps;
17688     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17689
17690     StartClockTimer(intendedTickLength);
17691 }
17692
17693 char *
17694 TimeString (long ms)
17695 {
17696     long second, minute, hour, day;
17697     char *sign = "";
17698     static char buf[32];
17699
17700     if (ms > 0 && ms <= 9900) {
17701       /* convert milliseconds to tenths, rounding up */
17702       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17703
17704       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17705       return buf;
17706     }
17707
17708     /* convert milliseconds to seconds, rounding up */
17709     /* use floating point to avoid strangeness of integer division
17710        with negative dividends on many machines */
17711     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17712
17713     if (second < 0) {
17714         sign = "-";
17715         second = -second;
17716     }
17717
17718     day = second / (60 * 60 * 24);
17719     second = second % (60 * 60 * 24);
17720     hour = second / (60 * 60);
17721     second = second % (60 * 60);
17722     minute = second / 60;
17723     second = second % 60;
17724
17725     if (day > 0)
17726       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17727               sign, day, hour, minute, second);
17728     else if (hour > 0)
17729       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17730     else
17731       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17732
17733     return buf;
17734 }
17735
17736
17737 /*
17738  * This is necessary because some C libraries aren't ANSI C compliant yet.
17739  */
17740 char *
17741 StrStr (char *string, char *match)
17742 {
17743     int i, length;
17744
17745     length = strlen(match);
17746
17747     for (i = strlen(string) - length; i >= 0; i--, string++)
17748       if (!strncmp(match, string, length))
17749         return string;
17750
17751     return NULL;
17752 }
17753
17754 char *
17755 StrCaseStr (char *string, char *match)
17756 {
17757     int i, j, length;
17758
17759     length = strlen(match);
17760
17761     for (i = strlen(string) - length; i >= 0; i--, string++) {
17762         for (j = 0; j < length; j++) {
17763             if (ToLower(match[j]) != ToLower(string[j]))
17764               break;
17765         }
17766         if (j == length) return string;
17767     }
17768
17769     return NULL;
17770 }
17771
17772 #ifndef _amigados
17773 int
17774 StrCaseCmp (char *s1, char *s2)
17775 {
17776     char c1, c2;
17777
17778     for (;;) {
17779         c1 = ToLower(*s1++);
17780         c2 = ToLower(*s2++);
17781         if (c1 > c2) return 1;
17782         if (c1 < c2) return -1;
17783         if (c1 == NULLCHAR) return 0;
17784     }
17785 }
17786
17787
17788 int
17789 ToLower (int c)
17790 {
17791     return isupper(c) ? tolower(c) : c;
17792 }
17793
17794
17795 int
17796 ToUpper (int c)
17797 {
17798     return islower(c) ? toupper(c) : c;
17799 }
17800 #endif /* !_amigados    */
17801
17802 char *
17803 StrSave (char *s)
17804 {
17805   char *ret;
17806
17807   if ((ret = (char *) malloc(strlen(s) + 1)))
17808     {
17809       safeStrCpy(ret, s, strlen(s)+1);
17810     }
17811   return ret;
17812 }
17813
17814 char *
17815 StrSavePtr (char *s, char **savePtr)
17816 {
17817     if (*savePtr) {
17818         free(*savePtr);
17819     }
17820     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17821       safeStrCpy(*savePtr, s, strlen(s)+1);
17822     }
17823     return(*savePtr);
17824 }
17825
17826 char *
17827 PGNDate ()
17828 {
17829     time_t clock;
17830     struct tm *tm;
17831     char buf[MSG_SIZ];
17832
17833     clock = time((time_t *)NULL);
17834     tm = localtime(&clock);
17835     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17836             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17837     return StrSave(buf);
17838 }
17839
17840
17841 char *
17842 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17843 {
17844     int i, j, fromX, fromY, toX, toY;
17845     int whiteToPlay;
17846     char buf[MSG_SIZ];
17847     char *p, *q;
17848     int emptycount;
17849     ChessSquare piece;
17850
17851     whiteToPlay = (gameMode == EditPosition) ?
17852       !blackPlaysFirst : (move % 2 == 0);
17853     p = buf;
17854
17855     /* Piece placement data */
17856     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17857         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17858         emptycount = 0;
17859         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17860             if (boards[move][i][j] == EmptySquare) {
17861                 emptycount++;
17862             } else { ChessSquare piece = boards[move][i][j];
17863                 if (emptycount > 0) {
17864                     if(emptycount<10) /* [HGM] can be >= 10 */
17865                         *p++ = '0' + emptycount;
17866                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17867                     emptycount = 0;
17868                 }
17869                 if(PieceToChar(piece) == '+') {
17870                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17871                     *p++ = '+';
17872                     piece = (ChessSquare)(CHUDEMOTED piece);
17873                 }
17874                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17875                 if(*p = PieceSuffix(piece)) p++;
17876                 if(p[-1] == '~') {
17877                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17878                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17879                     *p++ = '~';
17880                 }
17881             }
17882         }
17883         if (emptycount > 0) {
17884             if(emptycount<10) /* [HGM] can be >= 10 */
17885                 *p++ = '0' + emptycount;
17886             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17887             emptycount = 0;
17888         }
17889         *p++ = '/';
17890     }
17891     *(p - 1) = ' ';
17892
17893     /* [HGM] print Crazyhouse or Shogi holdings */
17894     if( gameInfo.holdingsWidth ) {
17895         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17896         q = p;
17897         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17898             piece = boards[move][i][BOARD_WIDTH-1];
17899             if( piece != EmptySquare )
17900               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17901                   *p++ = PieceToChar(piece);
17902         }
17903         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17904             piece = boards[move][BOARD_HEIGHT-i-1][0];
17905             if( piece != EmptySquare )
17906               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17907                   *p++ = PieceToChar(piece);
17908         }
17909
17910         if( q == p ) *p++ = '-';
17911         *p++ = ']';
17912         *p++ = ' ';
17913     }
17914
17915     /* Active color */
17916     *p++ = whiteToPlay ? 'w' : 'b';
17917     *p++ = ' ';
17918
17919   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17920     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17921   } else {
17922   if(nrCastlingRights) {
17923      int handW=0, handB=0;
17924      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17925         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17926         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17927      }
17928      q = p;
17929      if(appData.fischerCastling) {
17930         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17931            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17932                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17933         } else {
17934        /* [HGM] write directly from rights */
17935            if(boards[move][CASTLING][2] != NoRights &&
17936               boards[move][CASTLING][0] != NoRights   )
17937                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17938            if(boards[move][CASTLING][2] != NoRights &&
17939               boards[move][CASTLING][1] != NoRights   )
17940                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17941         }
17942         if(handB) {
17943            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17944                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17945         } else {
17946            if(boards[move][CASTLING][5] != NoRights &&
17947               boards[move][CASTLING][3] != NoRights   )
17948                 *p++ = boards[move][CASTLING][3] + AAA;
17949            if(boards[move][CASTLING][5] != NoRights &&
17950               boards[move][CASTLING][4] != NoRights   )
17951                 *p++ = boards[move][CASTLING][4] + AAA;
17952         }
17953      } else {
17954
17955         /* [HGM] write true castling rights */
17956         if( nrCastlingRights == 6 ) {
17957             int q, k=0;
17958             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17959                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17960             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17961                  boards[move][CASTLING][2] != NoRights  );
17962             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17963                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17964                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17965                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17966             }
17967             if(q) *p++ = 'Q';
17968             k = 0;
17969             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17970                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17971             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17972                  boards[move][CASTLING][5] != NoRights  );
17973             if(handB) {
17974                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17975                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17976                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17977             }
17978             if(q) *p++ = 'q';
17979         }
17980      }
17981      if (q == p) *p++ = '-'; /* No castling rights */
17982      *p++ = ' ';
17983   }
17984
17985   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17986      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17987      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17988     /* En passant target square */
17989     if (move > backwardMostMove) {
17990         fromX = moveList[move - 1][0] - AAA;
17991         fromY = moveList[move - 1][1] - ONE;
17992         toX = moveList[move - 1][2] - AAA;
17993         toY = moveList[move - 1][3] - ONE;
17994         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17995             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17996             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17997             fromX == toX) {
17998             /* 2-square pawn move just happened */
17999             *p++ = toX + AAA;
18000             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18001         } else {
18002             *p++ = '-';
18003         }
18004     } else if(move == backwardMostMove) {
18005         // [HGM] perhaps we should always do it like this, and forget the above?
18006         if((signed char)boards[move][EP_STATUS] >= 0) {
18007             *p++ = boards[move][EP_STATUS] + AAA;
18008             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18009         } else {
18010             *p++ = '-';
18011         }
18012     } else {
18013         *p++ = '-';
18014     }
18015     *p++ = ' ';
18016   }
18017   }
18018
18019     if(moveCounts)
18020     {   int i = 0, j=move;
18021
18022         /* [HGM] find reversible plies */
18023         if (appData.debugMode) { int k;
18024             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18025             for(k=backwardMostMove; k<=forwardMostMove; k++)
18026                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18027
18028         }
18029
18030         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18031         if( j == backwardMostMove ) i += initialRulePlies;
18032         sprintf(p, "%d ", i);
18033         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18034
18035         /* Fullmove number */
18036         sprintf(p, "%d", (move / 2) + 1);
18037     } else *--p = NULLCHAR;
18038
18039     return StrSave(buf);
18040 }
18041
18042 Boolean
18043 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18044 {
18045     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18046     char *p, c;
18047     int emptycount, virgin[BOARD_FILES];
18048     ChessSquare piece;
18049
18050     p = fen;
18051
18052     /* Piece placement data */
18053     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18054         j = 0;
18055         for (;;) {
18056             if (*p == '/' || *p == ' ' || *p == '[' ) {
18057                 if(j > w) w = j;
18058                 emptycount = gameInfo.boardWidth - j;
18059                 while (emptycount--)
18060                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18061                 if (*p == '/') p++;
18062                 else if(autoSize) { // we stumbled unexpectedly into end of board
18063                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18064                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18065                     }
18066                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18067                 }
18068                 break;
18069 #if(BOARD_FILES >= 10)*0
18070             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18071                 p++; emptycount=10;
18072                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18073                 while (emptycount--)
18074                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18075 #endif
18076             } else if (*p == '*') {
18077                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18078             } else if (isdigit(*p)) {
18079                 emptycount = *p++ - '0';
18080                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18081                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18082                 while (emptycount--)
18083                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18084             } else if (*p == '<') {
18085                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18086                 else if (i != 0 || !shuffle) return FALSE;
18087                 p++;
18088             } else if (shuffle && *p == '>') {
18089                 p++; // for now ignore closing shuffle range, and assume rank-end
18090             } else if (*p == '?') {
18091                 if (j >= gameInfo.boardWidth) return FALSE;
18092                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18093                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18094             } else if (*p == '+' || isalpha(*p)) {
18095                 char *q, *s = SUFFIXES;
18096                 if (j >= gameInfo.boardWidth) return FALSE;
18097                 if(*p=='+') {
18098                     char c = *++p;
18099                     if(q = strchr(s, p[1])) p++;
18100                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18101                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18102                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18103                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18104                 } else {
18105                     char c = *p++;
18106                     if(q = strchr(s, *p)) p++;
18107                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18108                 }
18109
18110                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18111                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18112                     piece = (ChessSquare) (PROMOTED piece);
18113                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18114                     p++;
18115                 }
18116                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18117                 if(piece == WhiteKing) wKingRank = i;
18118                 if(piece == BlackKing) bKingRank = i;
18119             } else {
18120                 return FALSE;
18121             }
18122         }
18123     }
18124     while (*p == '/' || *p == ' ') p++;
18125
18126     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18127
18128     /* [HGM] by default clear Crazyhouse holdings, if present */
18129     if(gameInfo.holdingsWidth) {
18130        for(i=0; i<BOARD_HEIGHT; i++) {
18131            board[i][0]             = EmptySquare; /* black holdings */
18132            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18133            board[i][1]             = (ChessSquare) 0; /* black counts */
18134            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18135        }
18136     }
18137
18138     /* [HGM] look for Crazyhouse holdings here */
18139     while(*p==' ') p++;
18140     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18141         int swap=0, wcnt=0, bcnt=0;
18142         if(*p == '[') p++;
18143         if(*p == '<') swap++, p++;
18144         if(*p == '-' ) p++; /* empty holdings */ else {
18145             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18146             /* if we would allow FEN reading to set board size, we would   */
18147             /* have to add holdings and shift the board read so far here   */
18148             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18149                 p++;
18150                 if((int) piece >= (int) BlackPawn ) {
18151                     i = (int)piece - (int)BlackPawn;
18152                     i = PieceToNumber((ChessSquare)i);
18153                     if( i >= gameInfo.holdingsSize ) return FALSE;
18154                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18155                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18156                     bcnt++;
18157                 } else {
18158                     i = (int)piece - (int)WhitePawn;
18159                     i = PieceToNumber((ChessSquare)i);
18160                     if( i >= gameInfo.holdingsSize ) return FALSE;
18161                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18162                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18163                     wcnt++;
18164                 }
18165             }
18166             if(subst) { // substitute back-rank question marks by holdings pieces
18167                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18168                     int k, m, n = bcnt + 1;
18169                     if(board[0][j] == ClearBoard) {
18170                         if(!wcnt) return FALSE;
18171                         n = rand() % wcnt;
18172                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18173                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18174                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18175                             break;
18176                         }
18177                     }
18178                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18179                         if(!bcnt) return FALSE;
18180                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18181                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18182                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18183                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18184                             break;
18185                         }
18186                     }
18187                 }
18188                 subst = 0;
18189             }
18190         }
18191         if(*p == ']') p++;
18192     }
18193
18194     if(subst) return FALSE; // substitution requested, but no holdings
18195
18196     while(*p == ' ') p++;
18197
18198     /* Active color */
18199     c = *p++;
18200     if(appData.colorNickNames) {
18201       if( c == appData.colorNickNames[0] ) c = 'w'; else
18202       if( c == appData.colorNickNames[1] ) c = 'b';
18203     }
18204     switch (c) {
18205       case 'w':
18206         *blackPlaysFirst = FALSE;
18207         break;
18208       case 'b':
18209         *blackPlaysFirst = TRUE;
18210         break;
18211       default:
18212         return FALSE;
18213     }
18214
18215     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18216     /* return the extra info in global variiables             */
18217
18218     while(*p==' ') p++;
18219
18220     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18221         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18222         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18223     }
18224
18225     /* set defaults in case FEN is incomplete */
18226     board[EP_STATUS] = EP_UNKNOWN;
18227     for(i=0; i<nrCastlingRights; i++ ) {
18228         board[CASTLING][i] =
18229             appData.fischerCastling ? NoRights : initialRights[i];
18230     }   /* assume possible unless obviously impossible */
18231     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18232     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18233     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18234                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18235     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18236     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18237     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18238                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18239     FENrulePlies = 0;
18240
18241     if(nrCastlingRights) {
18242       int fischer = 0;
18243       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18244       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18245           /* castling indicator present, so default becomes no castlings */
18246           for(i=0; i<nrCastlingRights; i++ ) {
18247                  board[CASTLING][i] = NoRights;
18248           }
18249       }
18250       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18251              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18252              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18253              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18254         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18255
18256         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18257             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18258             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18259         }
18260         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18261             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18262         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18263                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18264         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18265                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18266         switch(c) {
18267           case'K':
18268               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18269               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18270               board[CASTLING][2] = whiteKingFile;
18271               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18272               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18273               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18274               break;
18275           case'Q':
18276               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18277               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18278               board[CASTLING][2] = whiteKingFile;
18279               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18280               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18281               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18282               break;
18283           case'k':
18284               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18285               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18286               board[CASTLING][5] = blackKingFile;
18287               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18288               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18289               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18290               break;
18291           case'q':
18292               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18293               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18294               board[CASTLING][5] = blackKingFile;
18295               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18296               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18297               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18298           case '-':
18299               break;
18300           default: /* FRC castlings */
18301               if(c >= 'a') { /* black rights */
18302                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18303                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18304                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18305                   if(i == BOARD_RGHT) break;
18306                   board[CASTLING][5] = i;
18307                   c -= AAA;
18308                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18309                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18310                   if(c > i)
18311                       board[CASTLING][3] = c;
18312                   else
18313                       board[CASTLING][4] = c;
18314               } else { /* white rights */
18315                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18316                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18317                     if(board[0][i] == WhiteKing) break;
18318                   if(i == BOARD_RGHT) break;
18319                   board[CASTLING][2] = i;
18320                   c -= AAA - 'a' + 'A';
18321                   if(board[0][c] >= WhiteKing) break;
18322                   if(c > i)
18323                       board[CASTLING][0] = c;
18324                   else
18325                       board[CASTLING][1] = c;
18326               }
18327         }
18328       }
18329       for(i=0; i<nrCastlingRights; i++)
18330         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18331       if(gameInfo.variant == VariantSChess)
18332         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18333       if(fischer && shuffle) appData.fischerCastling = TRUE;
18334     if (appData.debugMode) {
18335         fprintf(debugFP, "FEN castling rights:");
18336         for(i=0; i<nrCastlingRights; i++)
18337         fprintf(debugFP, " %d", board[CASTLING][i]);
18338         fprintf(debugFP, "\n");
18339     }
18340
18341       while(*p==' ') p++;
18342     }
18343
18344     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18345
18346     /* read e.p. field in games that know e.p. capture */
18347     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18348        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18349        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18350       if(*p=='-') {
18351         p++; board[EP_STATUS] = EP_NONE;
18352       } else {
18353          char c = *p++ - AAA;
18354
18355          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18356          if(*p >= '0' && *p <='9') p++;
18357          board[EP_STATUS] = c;
18358       }
18359     }
18360
18361
18362     if(sscanf(p, "%d", &i) == 1) {
18363         FENrulePlies = i; /* 50-move ply counter */
18364         /* (The move number is still ignored)    */
18365     }
18366
18367     return TRUE;
18368 }
18369
18370 void
18371 EditPositionPasteFEN (char *fen)
18372 {
18373   if (fen != NULL) {
18374     Board initial_position;
18375
18376     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18377       DisplayError(_("Bad FEN position in clipboard"), 0);
18378       return ;
18379     } else {
18380       int savedBlackPlaysFirst = blackPlaysFirst;
18381       EditPositionEvent();
18382       blackPlaysFirst = savedBlackPlaysFirst;
18383       CopyBoard(boards[0], initial_position);
18384       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18385       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18386       DisplayBothClocks();
18387       DrawPosition(FALSE, boards[currentMove]);
18388     }
18389   }
18390 }
18391
18392 static char cseq[12] = "\\   ";
18393
18394 Boolean
18395 set_cont_sequence (char *new_seq)
18396 {
18397     int len;
18398     Boolean ret;
18399
18400     // handle bad attempts to set the sequence
18401         if (!new_seq)
18402                 return 0; // acceptable error - no debug
18403
18404     len = strlen(new_seq);
18405     ret = (len > 0) && (len < sizeof(cseq));
18406     if (ret)
18407       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18408     else if (appData.debugMode)
18409       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18410     return ret;
18411 }
18412
18413 /*
18414     reformat a source message so words don't cross the width boundary.  internal
18415     newlines are not removed.  returns the wrapped size (no null character unless
18416     included in source message).  If dest is NULL, only calculate the size required
18417     for the dest buffer.  lp argument indicats line position upon entry, and it's
18418     passed back upon exit.
18419 */
18420 int
18421 wrap (char *dest, char *src, int count, int width, int *lp)
18422 {
18423     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18424
18425     cseq_len = strlen(cseq);
18426     old_line = line = *lp;
18427     ansi = len = clen = 0;
18428
18429     for (i=0; i < count; i++)
18430     {
18431         if (src[i] == '\033')
18432             ansi = 1;
18433
18434         // if we hit the width, back up
18435         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18436         {
18437             // store i & len in case the word is too long
18438             old_i = i, old_len = len;
18439
18440             // find the end of the last word
18441             while (i && src[i] != ' ' && src[i] != '\n')
18442             {
18443                 i--;
18444                 len--;
18445             }
18446
18447             // word too long?  restore i & len before splitting it
18448             if ((old_i-i+clen) >= width)
18449             {
18450                 i = old_i;
18451                 len = old_len;
18452             }
18453
18454             // extra space?
18455             if (i && src[i-1] == ' ')
18456                 len--;
18457
18458             if (src[i] != ' ' && src[i] != '\n')
18459             {
18460                 i--;
18461                 if (len)
18462                     len--;
18463             }
18464
18465             // now append the newline and continuation sequence
18466             if (dest)
18467                 dest[len] = '\n';
18468             len++;
18469             if (dest)
18470                 strncpy(dest+len, cseq, cseq_len);
18471             len += cseq_len;
18472             line = cseq_len;
18473             clen = cseq_len;
18474             continue;
18475         }
18476
18477         if (dest)
18478             dest[len] = src[i];
18479         len++;
18480         if (!ansi)
18481             line++;
18482         if (src[i] == '\n')
18483             line = 0;
18484         if (src[i] == 'm')
18485             ansi = 0;
18486     }
18487     if (dest && appData.debugMode)
18488     {
18489         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18490             count, width, line, len, *lp);
18491         show_bytes(debugFP, src, count);
18492         fprintf(debugFP, "\ndest: ");
18493         show_bytes(debugFP, dest, len);
18494         fprintf(debugFP, "\n");
18495     }
18496     *lp = dest ? line : old_line;
18497
18498     return len;
18499 }
18500
18501 // [HGM] vari: routines for shelving variations
18502 Boolean modeRestore = FALSE;
18503
18504 void
18505 PushInner (int firstMove, int lastMove)
18506 {
18507         int i, j, nrMoves = lastMove - firstMove;
18508
18509         // push current tail of game on stack
18510         savedResult[storedGames] = gameInfo.result;
18511         savedDetails[storedGames] = gameInfo.resultDetails;
18512         gameInfo.resultDetails = NULL;
18513         savedFirst[storedGames] = firstMove;
18514         savedLast [storedGames] = lastMove;
18515         savedFramePtr[storedGames] = framePtr;
18516         framePtr -= nrMoves; // reserve space for the boards
18517         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18518             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18519             for(j=0; j<MOVE_LEN; j++)
18520                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18521             for(j=0; j<2*MOVE_LEN; j++)
18522                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18523             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18524             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18525             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18526             pvInfoList[firstMove+i-1].depth = 0;
18527             commentList[framePtr+i] = commentList[firstMove+i];
18528             commentList[firstMove+i] = NULL;
18529         }
18530
18531         storedGames++;
18532         forwardMostMove = firstMove; // truncate game so we can start variation
18533 }
18534
18535 void
18536 PushTail (int firstMove, int lastMove)
18537 {
18538         if(appData.icsActive) { // only in local mode
18539                 forwardMostMove = currentMove; // mimic old ICS behavior
18540                 return;
18541         }
18542         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18543
18544         PushInner(firstMove, lastMove);
18545         if(storedGames == 1) GreyRevert(FALSE);
18546         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18547 }
18548
18549 void
18550 PopInner (Boolean annotate)
18551 {
18552         int i, j, nrMoves;
18553         char buf[8000], moveBuf[20];
18554
18555         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18556         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18557         nrMoves = savedLast[storedGames] - currentMove;
18558         if(annotate) {
18559                 int cnt = 10;
18560                 if(!WhiteOnMove(currentMove))
18561                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18562                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18563                 for(i=currentMove; i<forwardMostMove; i++) {
18564                         if(WhiteOnMove(i))
18565                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18566                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18567                         strcat(buf, moveBuf);
18568                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18569                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18570                 }
18571                 strcat(buf, ")");
18572         }
18573         for(i=1; i<=nrMoves; i++) { // copy last variation back
18574             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18575             for(j=0; j<MOVE_LEN; j++)
18576                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18577             for(j=0; j<2*MOVE_LEN; j++)
18578                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18579             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18580             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18581             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18582             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18583             commentList[currentMove+i] = commentList[framePtr+i];
18584             commentList[framePtr+i] = NULL;
18585         }
18586         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18587         framePtr = savedFramePtr[storedGames];
18588         gameInfo.result = savedResult[storedGames];
18589         if(gameInfo.resultDetails != NULL) {
18590             free(gameInfo.resultDetails);
18591       }
18592         gameInfo.resultDetails = savedDetails[storedGames];
18593         forwardMostMove = currentMove + nrMoves;
18594 }
18595
18596 Boolean
18597 PopTail (Boolean annotate)
18598 {
18599         if(appData.icsActive) return FALSE; // only in local mode
18600         if(!storedGames) return FALSE; // sanity
18601         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18602
18603         PopInner(annotate);
18604         if(currentMove < forwardMostMove) ForwardEvent(); else
18605         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18606
18607         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18608         return TRUE;
18609 }
18610
18611 void
18612 CleanupTail ()
18613 {       // remove all shelved variations
18614         int i;
18615         for(i=0; i<storedGames; i++) {
18616             if(savedDetails[i])
18617                 free(savedDetails[i]);
18618             savedDetails[i] = NULL;
18619         }
18620         for(i=framePtr; i<MAX_MOVES; i++) {
18621                 if(commentList[i]) free(commentList[i]);
18622                 commentList[i] = NULL;
18623         }
18624         framePtr = MAX_MOVES-1;
18625         storedGames = 0;
18626 }
18627
18628 void
18629 LoadVariation (int index, char *text)
18630 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18631         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18632         int level = 0, move;
18633
18634         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18635         // first find outermost bracketing variation
18636         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18637             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18638                 if(*p == '{') wait = '}'; else
18639                 if(*p == '[') wait = ']'; else
18640                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18641                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18642             }
18643             if(*p == wait) wait = NULLCHAR; // closing ]} found
18644             p++;
18645         }
18646         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18647         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18648         end[1] = NULLCHAR; // clip off comment beyond variation
18649         ToNrEvent(currentMove-1);
18650         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18651         // kludge: use ParsePV() to append variation to game
18652         move = currentMove;
18653         ParsePV(start, TRUE, TRUE);
18654         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18655         ClearPremoveHighlights();
18656         CommentPopDown();
18657         ToNrEvent(currentMove+1);
18658 }
18659
18660 void
18661 LoadTheme ()
18662 {
18663     char *p, *q, buf[MSG_SIZ];
18664     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18665         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18666         ParseArgsFromString(buf);
18667         ActivateTheme(TRUE); // also redo colors
18668         return;
18669     }
18670     p = nickName;
18671     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18672     {
18673         int len;
18674         q = appData.themeNames;
18675         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18676       if(appData.useBitmaps) {
18677         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18678                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18679                 appData.liteBackTextureMode,
18680                 appData.darkBackTextureMode );
18681       } else {
18682         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18683                 Col2Text(2),   // lightSquareColor
18684                 Col2Text(3) ); // darkSquareColor
18685       }
18686       if(appData.useBorder) {
18687         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18688                 appData.border);
18689       } else {
18690         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18691       }
18692       if(appData.useFont) {
18693         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18694                 appData.renderPiecesWithFont,
18695                 appData.fontToPieceTable,
18696                 Col2Text(9),    // appData.fontBackColorWhite
18697                 Col2Text(10) ); // appData.fontForeColorBlack
18698       } else {
18699         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18700                 appData.pieceDirectory);
18701         if(!appData.pieceDirectory[0])
18702           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18703                 Col2Text(0),   // whitePieceColor
18704                 Col2Text(1) ); // blackPieceColor
18705       }
18706       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18707                 Col2Text(4),   // highlightSquareColor
18708                 Col2Text(5) ); // premoveHighlightColor
18709         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18710         if(insert != q) insert[-1] = NULLCHAR;
18711         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18712         if(q)   free(q);
18713     }
18714     ActivateTheme(FALSE);
18715 }