Improve reading of pieceToCharTable
[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)
5951 {
5952     int n = 0;
5953     while(*s) n += (*s != '\'' && *s != '"' && *s != '`' && *s != '!'), s++;
5954     return n;
5955 }
5956
5957 int
5958 SetCharTable (char *table, const char * map)
5959 /* [HGM] moved here from winboard.c because of its general usefulness */
5960 /*       Basically a safe strcpy that uses the last character as King */
5961 {
5962     int result = FALSE; int NrPieces;
5963
5964     if( map != NULL && (NrPieces=ptclen(map)) <= (int) EmptySquare
5965                     && NrPieces >= 12 && !(NrPieces&1)) {
5966         int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
5967
5968         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5969         for( i=0; i<NrPieces/2-1; i++ ) {
5970             if(map[j] == ':') i = CHUPROMOTED WhitePawn, j++;
5971             table[i] = map[j++];
5972             if(map[j] == '\'') table[i] += 64;
5973             if(map[j] == '!') table[i] += 128;
5974         }
5975         table[(int) WhiteKing]  = map[j++];
5976         for( i=0; i<NrPieces/2-1; i++ ) {
5977             if(map[j] == ':') i = CHUPROMOTED BlackPawn, j++;
5978             table[WHITE_TO_BLACK i] = map[j++];
5979             if(map[j] == '\'') table[WHITE_TO_BLACK i] += 64;
5980             if(map[j] == '!') table[WHITE_TO_BLACK i] += 128;
5981         }
5982         table[(int) BlackKing]  = map[j++];
5983
5984         result = TRUE;
5985     }
5986
5987     return result;
5988 }
5989
5990 void
5991 Prelude (Board board)
5992 {       // [HGM] superchess: random selection of exo-pieces
5993         int i, j, k; ChessSquare p;
5994         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5995
5996         GetPositionNumber(); // use FRC position number
5997
5998         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5999             SetCharTable(pieceToChar, appData.pieceToCharTable);
6000             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6001                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6002         }
6003
6004         j = seed%4;                 seed /= 4;
6005         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6006         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6007         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6008         j = seed%3 + (seed%3 >= j); seed /= 3;
6009         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6010         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6011         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6012         j = seed%3;                 seed /= 3;
6013         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6014         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6015         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6016         j = seed%2 + (seed%2 >= j); seed /= 2;
6017         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6018         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6019         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6020         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6021         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6022         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6023         put(board, exoPieces[0],    0, 0, ANY);
6024         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6025 }
6026
6027 void
6028 InitPosition (int redraw)
6029 {
6030     ChessSquare (* pieces)[BOARD_FILES];
6031     int i, j, pawnRow=1, pieceRows=1, overrule,
6032     oldx = gameInfo.boardWidth,
6033     oldy = gameInfo.boardHeight,
6034     oldh = gameInfo.holdingsWidth;
6035     static int oldv;
6036
6037     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6038
6039     /* [AS] Initialize pv info list [HGM] and game status */
6040     {
6041         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6042             pvInfoList[i].depth = 0;
6043             boards[i][EP_STATUS] = EP_NONE;
6044             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6045         }
6046
6047         initialRulePlies = 0; /* 50-move counter start */
6048
6049         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6050         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6051     }
6052
6053
6054     /* [HGM] logic here is completely changed. In stead of full positions */
6055     /* the initialized data only consist of the two backranks. The switch */
6056     /* selects which one we will use, which is than copied to the Board   */
6057     /* initialPosition, which for the rest is initialized by Pawns and    */
6058     /* empty squares. This initial position is then copied to boards[0],  */
6059     /* possibly after shuffling, so that it remains available.            */
6060
6061     gameInfo.holdingsWidth = 0; /* default board sizes */
6062     gameInfo.boardWidth    = 8;
6063     gameInfo.boardHeight   = 8;
6064     gameInfo.holdingsSize  = 0;
6065     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6066     for(i=0; i<BOARD_FILES-6; i++)
6067       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6068     initialPosition[EP_STATUS] = EP_NONE;
6069     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6070     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6071     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6072          SetCharTable(pieceNickName, appData.pieceNickNames);
6073     else SetCharTable(pieceNickName, "............");
6074     pieces = FIDEArray;
6075
6076     switch (gameInfo.variant) {
6077     case VariantFischeRandom:
6078       shuffleOpenings = TRUE;
6079       appData.fischerCastling = TRUE;
6080     default:
6081       break;
6082     case VariantShatranj:
6083       pieces = ShatranjArray;
6084       nrCastlingRights = 0;
6085       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6086       break;
6087     case VariantMakruk:
6088       pieces = makrukArray;
6089       nrCastlingRights = 0;
6090       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6091       break;
6092     case VariantASEAN:
6093       pieces = aseanArray;
6094       nrCastlingRights = 0;
6095       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6096       break;
6097     case VariantTwoKings:
6098       pieces = twoKingsArray;
6099       break;
6100     case VariantGrand:
6101       pieces = GrandArray;
6102       nrCastlingRights = 0;
6103       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6104       gameInfo.boardWidth = 10;
6105       gameInfo.boardHeight = 10;
6106       gameInfo.holdingsSize = 7;
6107       break;
6108     case VariantCapaRandom:
6109       shuffleOpenings = TRUE;
6110       appData.fischerCastling = TRUE;
6111     case VariantCapablanca:
6112       pieces = CapablancaArray;
6113       gameInfo.boardWidth = 10;
6114       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6115       break;
6116     case VariantGothic:
6117       pieces = GothicArray;
6118       gameInfo.boardWidth = 10;
6119       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6120       break;
6121     case VariantSChess:
6122       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6123       gameInfo.holdingsSize = 7;
6124       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6125       break;
6126     case VariantJanus:
6127       pieces = JanusArray;
6128       gameInfo.boardWidth = 10;
6129       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6130       nrCastlingRights = 6;
6131         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6132         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6133         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6134         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6135         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6136         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6137       break;
6138     case VariantFalcon:
6139       pieces = FalconArray;
6140       gameInfo.boardWidth = 10;
6141       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6142       break;
6143     case VariantXiangqi:
6144       pieces = XiangqiArray;
6145       gameInfo.boardWidth  = 9;
6146       gameInfo.boardHeight = 10;
6147       nrCastlingRights = 0;
6148       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6149       break;
6150     case VariantShogi:
6151       pieces = ShogiArray;
6152       gameInfo.boardWidth  = 9;
6153       gameInfo.boardHeight = 9;
6154       gameInfo.holdingsSize = 7;
6155       nrCastlingRights = 0;
6156       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6157       break;
6158     case VariantChu:
6159       pieces = ChuArray; pieceRows = 3;
6160       gameInfo.boardWidth  = 12;
6161       gameInfo.boardHeight = 12;
6162       nrCastlingRights = 0;
6163       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6164                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6165       break;
6166     case VariantCourier:
6167       pieces = CourierArray;
6168       gameInfo.boardWidth  = 12;
6169       nrCastlingRights = 0;
6170       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6171       break;
6172     case VariantKnightmate:
6173       pieces = KnightmateArray;
6174       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6175       break;
6176     case VariantSpartan:
6177       pieces = SpartanArray;
6178       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6179       break;
6180     case VariantLion:
6181       pieces = lionArray;
6182       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6183       break;
6184     case VariantChuChess:
6185       pieces = ChuChessArray;
6186       gameInfo.boardWidth = 10;
6187       gameInfo.boardHeight = 10;
6188       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6189       break;
6190     case VariantFairy:
6191       pieces = fairyArray;
6192       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6193       break;
6194     case VariantGreat:
6195       pieces = GreatArray;
6196       gameInfo.boardWidth = 10;
6197       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6198       gameInfo.holdingsSize = 8;
6199       break;
6200     case VariantSuper:
6201       pieces = FIDEArray;
6202       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6203       gameInfo.holdingsSize = 8;
6204       startedFromSetupPosition = TRUE;
6205       break;
6206     case VariantCrazyhouse:
6207     case VariantBughouse:
6208       pieces = FIDEArray;
6209       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6210       gameInfo.holdingsSize = 5;
6211       break;
6212     case VariantWildCastle:
6213       pieces = FIDEArray;
6214       /* !!?shuffle with kings guaranteed to be on d or e file */
6215       shuffleOpenings = 1;
6216       break;
6217     case VariantNoCastle:
6218       pieces = FIDEArray;
6219       nrCastlingRights = 0;
6220       /* !!?unconstrained back-rank shuffle */
6221       shuffleOpenings = 1;
6222       break;
6223     }
6224
6225     overrule = 0;
6226     if(appData.NrFiles >= 0) {
6227         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6228         gameInfo.boardWidth = appData.NrFiles;
6229     }
6230     if(appData.NrRanks >= 0) {
6231         gameInfo.boardHeight = appData.NrRanks;
6232     }
6233     if(appData.holdingsSize >= 0) {
6234         i = appData.holdingsSize;
6235         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6236         gameInfo.holdingsSize = i;
6237     }
6238     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6239     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6240         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6241
6242     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6243     if(pawnRow < 1) pawnRow = 1;
6244     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6245        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6246     if(gameInfo.variant == VariantChu) pawnRow = 3;
6247
6248     /* User pieceToChar list overrules defaults */
6249     if(appData.pieceToCharTable != NULL)
6250         SetCharTable(pieceToChar, appData.pieceToCharTable);
6251
6252     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6253
6254         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6255             s = (ChessSquare) 0; /* account holding counts in guard band */
6256         for( i=0; i<BOARD_HEIGHT; i++ )
6257             initialPosition[i][j] = s;
6258
6259         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6260         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6261         initialPosition[pawnRow][j] = WhitePawn;
6262         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6263         if(gameInfo.variant == VariantXiangqi) {
6264             if(j&1) {
6265                 initialPosition[pawnRow][j] =
6266                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6267                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6268                    initialPosition[2][j] = WhiteCannon;
6269                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6270                 }
6271             }
6272         }
6273         if(gameInfo.variant == VariantChu) {
6274              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6275                initialPosition[pawnRow+1][j] = WhiteCobra,
6276                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6277              for(i=1; i<pieceRows; i++) {
6278                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6279                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6280              }
6281         }
6282         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6283             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6284                initialPosition[0][j] = WhiteRook;
6285                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6286             }
6287         }
6288         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6289     }
6290     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6291     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6292
6293             j=BOARD_LEFT+1;
6294             initialPosition[1][j] = WhiteBishop;
6295             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6296             j=BOARD_RGHT-2;
6297             initialPosition[1][j] = WhiteRook;
6298             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6299     }
6300
6301     if( nrCastlingRights == -1) {
6302         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6303         /*       This sets default castling rights from none to normal corners   */
6304         /* Variants with other castling rights must set them themselves above    */
6305         nrCastlingRights = 6;
6306
6307         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6308         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6309         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6310         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6311         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6312         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6313      }
6314
6315      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6316      if(gameInfo.variant == VariantGreat) { // promotion commoners
6317         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6318         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6319         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6320         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6321      }
6322      if( gameInfo.variant == VariantSChess ) {
6323       initialPosition[1][0] = BlackMarshall;
6324       initialPosition[2][0] = BlackAngel;
6325       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6326       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6327       initialPosition[1][1] = initialPosition[2][1] =
6328       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6329      }
6330   if (appData.debugMode) {
6331     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6332   }
6333     if(shuffleOpenings) {
6334         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6335         startedFromSetupPosition = TRUE;
6336     }
6337     if(startedFromPositionFile) {
6338       /* [HGM] loadPos: use PositionFile for every new game */
6339       CopyBoard(initialPosition, filePosition);
6340       for(i=0; i<nrCastlingRights; i++)
6341           initialRights[i] = filePosition[CASTLING][i];
6342       startedFromSetupPosition = TRUE;
6343     }
6344
6345     CopyBoard(boards[0], initialPosition);
6346
6347     if(oldx != gameInfo.boardWidth ||
6348        oldy != gameInfo.boardHeight ||
6349        oldv != gameInfo.variant ||
6350        oldh != gameInfo.holdingsWidth
6351                                          )
6352             InitDrawingSizes(-2 ,0);
6353
6354     oldv = gameInfo.variant;
6355     if (redraw)
6356       DrawPosition(TRUE, boards[currentMove]);
6357 }
6358
6359 void
6360 SendBoard (ChessProgramState *cps, int moveNum)
6361 {
6362     char message[MSG_SIZ];
6363
6364     if (cps->useSetboard) {
6365       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6366       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6367       SendToProgram(message, cps);
6368       free(fen);
6369
6370     } else {
6371       ChessSquare *bp;
6372       int i, j, left=0, right=BOARD_WIDTH;
6373       /* Kludge to set black to move, avoiding the troublesome and now
6374        * deprecated "black" command.
6375        */
6376       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6377         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6378
6379       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6380
6381       SendToProgram("edit\n", cps);
6382       SendToProgram("#\n", cps);
6383       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6384         bp = &boards[moveNum][i][left];
6385         for (j = left; j < right; j++, bp++) {
6386           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6387           if ((int) *bp < (int) BlackPawn) {
6388             if(j == BOARD_RGHT+1)
6389                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6390             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6391             if(message[0] == '+' || message[0] == '~') {
6392               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6393                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6394                         AAA + j, ONE + i);
6395             }
6396             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6397                 message[1] = BOARD_RGHT   - 1 - j + '1';
6398                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6399             }
6400             SendToProgram(message, cps);
6401           }
6402         }
6403       }
6404
6405       SendToProgram("c\n", cps);
6406       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6407         bp = &boards[moveNum][i][left];
6408         for (j = left; j < right; j++, bp++) {
6409           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6410           if (((int) *bp != (int) EmptySquare)
6411               && ((int) *bp >= (int) BlackPawn)) {
6412             if(j == BOARD_LEFT-2)
6413                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6414             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6415                     AAA + j, ONE + i);
6416             if(message[0] == '+' || message[0] == '~') {
6417               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6418                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6419                         AAA + j, ONE + i);
6420             }
6421             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6422                 message[1] = BOARD_RGHT   - 1 - j + '1';
6423                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6424             }
6425             SendToProgram(message, cps);
6426           }
6427         }
6428       }
6429
6430       SendToProgram(".\n", cps);
6431     }
6432     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6433 }
6434
6435 char exclusionHeader[MSG_SIZ];
6436 int exCnt, excludePtr;
6437 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6438 static Exclusion excluTab[200];
6439 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6440
6441 static void
6442 WriteMap (int s)
6443 {
6444     int j;
6445     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6446     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6447 }
6448
6449 static void
6450 ClearMap ()
6451 {
6452     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6453     excludePtr = 24; exCnt = 0;
6454     WriteMap(0);
6455 }
6456
6457 static void
6458 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6459 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6460     char buf[2*MOVE_LEN], *p;
6461     Exclusion *e = excluTab;
6462     int i;
6463     for(i=0; i<exCnt; i++)
6464         if(e[i].ff == fromX && e[i].fr == fromY &&
6465            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6466     if(i == exCnt) { // was not in exclude list; add it
6467         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6468         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6469             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6470             return; // abort
6471         }
6472         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6473         excludePtr++; e[i].mark = excludePtr++;
6474         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6475         exCnt++;
6476     }
6477     exclusionHeader[e[i].mark] = state;
6478 }
6479
6480 static int
6481 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6482 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6483     char buf[MSG_SIZ];
6484     int j, k;
6485     ChessMove moveType;
6486     if((signed char)promoChar == -1) { // kludge to indicate best move
6487         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6488             return 1; // if unparsable, abort
6489     }
6490     // update exclusion map (resolving toggle by consulting existing state)
6491     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6492     j = k%8; k >>= 3;
6493     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6494     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6495          excludeMap[k] |=   1<<j;
6496     else excludeMap[k] &= ~(1<<j);
6497     // update header
6498     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6499     // inform engine
6500     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6501     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6502     SendToBoth(buf);
6503     return (state == '+');
6504 }
6505
6506 static void
6507 ExcludeClick (int index)
6508 {
6509     int i, j;
6510     Exclusion *e = excluTab;
6511     if(index < 25) { // none, best or tail clicked
6512         if(index < 13) { // none: include all
6513             WriteMap(0); // clear map
6514             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6515             SendToBoth("include all\n"); // and inform engine
6516         } else if(index > 18) { // tail
6517             if(exclusionHeader[19] == '-') { // tail was excluded
6518                 SendToBoth("include all\n");
6519                 WriteMap(0); // clear map completely
6520                 // now re-exclude selected moves
6521                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6522                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6523             } else { // tail was included or in mixed state
6524                 SendToBoth("exclude all\n");
6525                 WriteMap(0xFF); // fill map completely
6526                 // now re-include selected moves
6527                 j = 0; // count them
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, '+'), j++;
6530                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6531             }
6532         } else { // best
6533             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6534         }
6535     } else {
6536         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6537             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6538             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6539             break;
6540         }
6541     }
6542 }
6543
6544 ChessSquare
6545 DefaultPromoChoice (int white)
6546 {
6547     ChessSquare result;
6548     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6549        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6550         result = WhiteFerz; // no choice
6551     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6552         result= WhiteKing; // in Suicide Q is the last thing we want
6553     else if(gameInfo.variant == VariantSpartan)
6554         result = white ? WhiteQueen : WhiteAngel;
6555     else result = WhiteQueen;
6556     if(!white) result = WHITE_TO_BLACK result;
6557     return result;
6558 }
6559
6560 static int autoQueen; // [HGM] oneclick
6561
6562 int
6563 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6564 {
6565     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6566     /* [HGM] add Shogi promotions */
6567     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6568     ChessSquare piece, partner;
6569     ChessMove moveType;
6570     Boolean premove;
6571
6572     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6573     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6574
6575     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6576       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6577         return FALSE;
6578
6579     piece = boards[currentMove][fromY][fromX];
6580     if(gameInfo.variant == VariantChu) {
6581         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6582         promotionZoneSize = BOARD_HEIGHT/3;
6583         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6584     } else if(gameInfo.variant == VariantShogi) {
6585         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6586         highestPromotingPiece = (int)WhiteAlfil;
6587     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6588         promotionZoneSize = 3;
6589     }
6590
6591     // Treat Lance as Pawn when it is not representing Amazon or Lance
6592     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6593         if(piece == WhiteLance) piece = WhitePawn; else
6594         if(piece == BlackLance) piece = BlackPawn;
6595     }
6596
6597     // next weed out all moves that do not touch the promotion zone at all
6598     if((int)piece >= BlackPawn) {
6599         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6600              return FALSE;
6601         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6602         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6603     } else {
6604         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6605            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6606         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6607              return FALSE;
6608     }
6609
6610     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6611
6612     // weed out mandatory Shogi promotions
6613     if(gameInfo.variant == VariantShogi) {
6614         if(piece >= BlackPawn) {
6615             if(toY == 0 && piece == BlackPawn ||
6616                toY == 0 && piece == BlackQueen ||
6617                toY <= 1 && piece == BlackKnight) {
6618                 *promoChoice = '+';
6619                 return FALSE;
6620             }
6621         } else {
6622             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6623                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6624                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6625                 *promoChoice = '+';
6626                 return FALSE;
6627             }
6628         }
6629     }
6630
6631     // weed out obviously illegal Pawn moves
6632     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6633         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6634         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6635         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6636         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6637         // note we are not allowed to test for valid (non-)capture, due to premove
6638     }
6639
6640     // we either have a choice what to promote to, or (in Shogi) whether to promote
6641     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6642        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6643         ChessSquare p=BlackFerz;  // no choice
6644         while(p < EmptySquare) {  //but make sure we use piece that exists
6645             *promoChoice = PieceToChar(p++);
6646             if(*promoChoice != '.') break;
6647         }
6648         return FALSE;
6649     }
6650     // no sense asking what we must promote to if it is going to explode...
6651     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6652         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6653         return FALSE;
6654     }
6655     // give caller the default choice even if we will not make it
6656     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6657     partner = piece; // pieces can promote if the pieceToCharTable says so
6658     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6659     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6660     if(        sweepSelect && gameInfo.variant != VariantGreat
6661                            && gameInfo.variant != VariantGrand
6662                            && gameInfo.variant != VariantSuper) return FALSE;
6663     if(autoQueen) return FALSE; // predetermined
6664
6665     // suppress promotion popup on illegal moves that are not premoves
6666     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6667               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6668     if(appData.testLegality && !premove) {
6669         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6670                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6671         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6672         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6673             return FALSE;
6674     }
6675
6676     return TRUE;
6677 }
6678
6679 int
6680 InPalace (int row, int column)
6681 {   /* [HGM] for Xiangqi */
6682     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6683          column < (BOARD_WIDTH + 4)/2 &&
6684          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6685     return FALSE;
6686 }
6687
6688 int
6689 PieceForSquare (int x, int y)
6690 {
6691   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6692      return -1;
6693   else
6694      return boards[currentMove][y][x];
6695 }
6696
6697 int
6698 OKToStartUserMove (int x, int y)
6699 {
6700     ChessSquare from_piece;
6701     int white_piece;
6702
6703     if (matchMode) return FALSE;
6704     if (gameMode == EditPosition) return TRUE;
6705
6706     if (x >= 0 && y >= 0)
6707       from_piece = boards[currentMove][y][x];
6708     else
6709       from_piece = EmptySquare;
6710
6711     if (from_piece == EmptySquare) return FALSE;
6712
6713     white_piece = (int)from_piece >= (int)WhitePawn &&
6714       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6715
6716     switch (gameMode) {
6717       case AnalyzeFile:
6718       case TwoMachinesPlay:
6719       case EndOfGame:
6720         return FALSE;
6721
6722       case IcsObserving:
6723       case IcsIdle:
6724         return FALSE;
6725
6726       case MachinePlaysWhite:
6727       case IcsPlayingBlack:
6728         if (appData.zippyPlay) return FALSE;
6729         if (white_piece) {
6730             DisplayMoveError(_("You are playing Black"));
6731             return FALSE;
6732         }
6733         break;
6734
6735       case MachinePlaysBlack:
6736       case IcsPlayingWhite:
6737         if (appData.zippyPlay) return FALSE;
6738         if (!white_piece) {
6739             DisplayMoveError(_("You are playing White"));
6740             return FALSE;
6741         }
6742         break;
6743
6744       case PlayFromGameFile:
6745             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6746       case EditGame:
6747         if (!white_piece && WhiteOnMove(currentMove)) {
6748             DisplayMoveError(_("It is White's turn"));
6749             return FALSE;
6750         }
6751         if (white_piece && !WhiteOnMove(currentMove)) {
6752             DisplayMoveError(_("It is Black's turn"));
6753             return FALSE;
6754         }
6755         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6756             /* Editing correspondence game history */
6757             /* Could disallow this or prompt for confirmation */
6758             cmailOldMove = -1;
6759         }
6760         break;
6761
6762       case BeginningOfGame:
6763         if (appData.icsActive) return FALSE;
6764         if (!appData.noChessProgram) {
6765             if (!white_piece) {
6766                 DisplayMoveError(_("You are playing White"));
6767                 return FALSE;
6768             }
6769         }
6770         break;
6771
6772       case Training:
6773         if (!white_piece && WhiteOnMove(currentMove)) {
6774             DisplayMoveError(_("It is White's turn"));
6775             return FALSE;
6776         }
6777         if (white_piece && !WhiteOnMove(currentMove)) {
6778             DisplayMoveError(_("It is Black's turn"));
6779             return FALSE;
6780         }
6781         break;
6782
6783       default:
6784       case IcsExamining:
6785         break;
6786     }
6787     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6788         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6789         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6790         && gameMode != AnalyzeFile && gameMode != Training) {
6791         DisplayMoveError(_("Displayed position is not current"));
6792         return FALSE;
6793     }
6794     return TRUE;
6795 }
6796
6797 Boolean
6798 OnlyMove (int *x, int *y, Boolean captures)
6799 {
6800     DisambiguateClosure cl;
6801     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6802     switch(gameMode) {
6803       case MachinePlaysBlack:
6804       case IcsPlayingWhite:
6805       case BeginningOfGame:
6806         if(!WhiteOnMove(currentMove)) return FALSE;
6807         break;
6808       case MachinePlaysWhite:
6809       case IcsPlayingBlack:
6810         if(WhiteOnMove(currentMove)) return FALSE;
6811         break;
6812       case EditGame:
6813         break;
6814       default:
6815         return FALSE;
6816     }
6817     cl.pieceIn = EmptySquare;
6818     cl.rfIn = *y;
6819     cl.ffIn = *x;
6820     cl.rtIn = -1;
6821     cl.ftIn = -1;
6822     cl.promoCharIn = NULLCHAR;
6823     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6824     if( cl.kind == NormalMove ||
6825         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6826         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6827         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6828       fromX = cl.ff;
6829       fromY = cl.rf;
6830       *x = cl.ft;
6831       *y = cl.rt;
6832       return TRUE;
6833     }
6834     if(cl.kind != ImpossibleMove) return FALSE;
6835     cl.pieceIn = EmptySquare;
6836     cl.rfIn = -1;
6837     cl.ffIn = -1;
6838     cl.rtIn = *y;
6839     cl.ftIn = *x;
6840     cl.promoCharIn = NULLCHAR;
6841     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6842     if( cl.kind == NormalMove ||
6843         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6844         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6845         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6846       fromX = cl.ff;
6847       fromY = cl.rf;
6848       *x = cl.ft;
6849       *y = cl.rt;
6850       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6851       return TRUE;
6852     }
6853     return FALSE;
6854 }
6855
6856 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6857 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6858 int lastLoadGameUseList = FALSE;
6859 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6860 ChessMove lastLoadGameStart = EndOfFile;
6861 int doubleClick;
6862 Boolean addToBookFlag;
6863
6864 void
6865 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6866 {
6867     ChessMove moveType;
6868     ChessSquare pup;
6869     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6870
6871     /* Check if the user is playing in turn.  This is complicated because we
6872        let the user "pick up" a piece before it is his turn.  So the piece he
6873        tried to pick up may have been captured by the time he puts it down!
6874        Therefore we use the color the user is supposed to be playing in this
6875        test, not the color of the piece that is currently on the starting
6876        square---except in EditGame mode, where the user is playing both
6877        sides; fortunately there the capture race can't happen.  (It can
6878        now happen in IcsExamining mode, but that's just too bad.  The user
6879        will get a somewhat confusing message in that case.)
6880        */
6881
6882     switch (gameMode) {
6883       case AnalyzeFile:
6884       case TwoMachinesPlay:
6885       case EndOfGame:
6886       case IcsObserving:
6887       case IcsIdle:
6888         /* We switched into a game mode where moves are not accepted,
6889            perhaps while the mouse button was down. */
6890         return;
6891
6892       case MachinePlaysWhite:
6893         /* User is moving for Black */
6894         if (WhiteOnMove(currentMove)) {
6895             DisplayMoveError(_("It is White's turn"));
6896             return;
6897         }
6898         break;
6899
6900       case MachinePlaysBlack:
6901         /* User is moving for White */
6902         if (!WhiteOnMove(currentMove)) {
6903             DisplayMoveError(_("It is Black's turn"));
6904             return;
6905         }
6906         break;
6907
6908       case PlayFromGameFile:
6909             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6910       case EditGame:
6911       case IcsExamining:
6912       case BeginningOfGame:
6913       case AnalyzeMode:
6914       case Training:
6915         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6916         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6917             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6918             /* User is moving for Black */
6919             if (WhiteOnMove(currentMove)) {
6920                 DisplayMoveError(_("It is White's turn"));
6921                 return;
6922             }
6923         } else {
6924             /* User is moving for White */
6925             if (!WhiteOnMove(currentMove)) {
6926                 DisplayMoveError(_("It is Black's turn"));
6927                 return;
6928             }
6929         }
6930         break;
6931
6932       case IcsPlayingBlack:
6933         /* User is moving for Black */
6934         if (WhiteOnMove(currentMove)) {
6935             if (!appData.premove) {
6936                 DisplayMoveError(_("It is White's turn"));
6937             } else if (toX >= 0 && toY >= 0) {
6938                 premoveToX = toX;
6939                 premoveToY = toY;
6940                 premoveFromX = fromX;
6941                 premoveFromY = fromY;
6942                 premovePromoChar = promoChar;
6943                 gotPremove = 1;
6944                 if (appData.debugMode)
6945                     fprintf(debugFP, "Got premove: fromX %d,"
6946                             "fromY %d, toX %d, toY %d\n",
6947                             fromX, fromY, toX, toY);
6948             }
6949             return;
6950         }
6951         break;
6952
6953       case IcsPlayingWhite:
6954         /* User is moving for White */
6955         if (!WhiteOnMove(currentMove)) {
6956             if (!appData.premove) {
6957                 DisplayMoveError(_("It is Black's turn"));
6958             } else if (toX >= 0 && toY >= 0) {
6959                 premoveToX = toX;
6960                 premoveToY = toY;
6961                 premoveFromX = fromX;
6962                 premoveFromY = fromY;
6963                 premovePromoChar = promoChar;
6964                 gotPremove = 1;
6965                 if (appData.debugMode)
6966                     fprintf(debugFP, "Got premove: fromX %d,"
6967                             "fromY %d, toX %d, toY %d\n",
6968                             fromX, fromY, toX, toY);
6969             }
6970             return;
6971         }
6972         break;
6973
6974       default:
6975         break;
6976
6977       case EditPosition:
6978         /* EditPosition, empty square, or different color piece;
6979            click-click move is possible */
6980         if (toX == -2 || toY == -2) {
6981             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
6982             DrawPosition(FALSE, boards[currentMove]);
6983             return;
6984         } else if (toX >= 0 && toY >= 0) {
6985             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6986                 ChessSquare q, p = boards[0][rf][ff];
6987                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6988                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6989                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6990                 if(PieceToChar(q) == '+') gatingPiece = p;
6991             }
6992             boards[0][toY][toX] = boards[0][fromY][fromX];
6993             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6994                 if(boards[0][fromY][0] != EmptySquare) {
6995                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6996                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6997                 }
6998             } else
6999             if(fromX == BOARD_RGHT+1) {
7000                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7001                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7002                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7003                 }
7004             } else
7005             boards[0][fromY][fromX] = gatingPiece;
7006             DrawPosition(FALSE, boards[currentMove]);
7007             return;
7008         }
7009         return;
7010     }
7011
7012     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7013     pup = boards[currentMove][toY][toX];
7014
7015     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7016     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7017          if( pup != EmptySquare ) return;
7018          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7019            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7020                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7021            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7022            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7023            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7024            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7025          fromY = DROP_RANK;
7026     }
7027
7028     /* [HGM] always test for legality, to get promotion info */
7029     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7030                                          fromY, fromX, toY, toX, promoChar);
7031
7032     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7033
7034     /* [HGM] but possibly ignore an IllegalMove result */
7035     if (appData.testLegality) {
7036         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7037             DisplayMoveError(_("Illegal move"));
7038             return;
7039         }
7040     }
7041
7042     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7043         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7044              ClearPremoveHighlights(); // was included
7045         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7046         return;
7047     }
7048
7049     if(addToBookFlag) { // adding moves to book
7050         char buf[MSG_SIZ], move[MSG_SIZ];
7051         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7052         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7053         AddBookMove(buf);
7054         addToBookFlag = FALSE;
7055         ClearHighlights();
7056         return;
7057     }
7058
7059     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7060 }
7061
7062 /* Common tail of UserMoveEvent and DropMenuEvent */
7063 int
7064 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7065 {
7066     char *bookHit = 0;
7067
7068     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7069         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7070         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7071         if(WhiteOnMove(currentMove)) {
7072             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7073         } else {
7074             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7075         }
7076     }
7077
7078     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7079        move type in caller when we know the move is a legal promotion */
7080     if(moveType == NormalMove && promoChar)
7081         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7082
7083     /* [HGM] <popupFix> The following if has been moved here from
7084        UserMoveEvent(). Because it seemed to belong here (why not allow
7085        piece drops in training games?), and because it can only be
7086        performed after it is known to what we promote. */
7087     if (gameMode == Training) {
7088       /* compare the move played on the board to the next move in the
7089        * game. If they match, display the move and the opponent's response.
7090        * If they don't match, display an error message.
7091        */
7092       int saveAnimate;
7093       Board testBoard;
7094       CopyBoard(testBoard, boards[currentMove]);
7095       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7096
7097       if (CompareBoards(testBoard, boards[currentMove+1])) {
7098         ForwardInner(currentMove+1);
7099
7100         /* Autoplay the opponent's response.
7101          * if appData.animate was TRUE when Training mode was entered,
7102          * the response will be animated.
7103          */
7104         saveAnimate = appData.animate;
7105         appData.animate = animateTraining;
7106         ForwardInner(currentMove+1);
7107         appData.animate = saveAnimate;
7108
7109         /* check for the end of the game */
7110         if (currentMove >= forwardMostMove) {
7111           gameMode = PlayFromGameFile;
7112           ModeHighlight();
7113           SetTrainingModeOff();
7114           DisplayInformation(_("End of game"));
7115         }
7116       } else {
7117         DisplayError(_("Incorrect move"), 0);
7118       }
7119       return 1;
7120     }
7121
7122   /* Ok, now we know that the move is good, so we can kill
7123      the previous line in Analysis Mode */
7124   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7125                                 && currentMove < forwardMostMove) {
7126     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7127     else forwardMostMove = currentMove;
7128   }
7129
7130   ClearMap();
7131
7132   /* If we need the chess program but it's dead, restart it */
7133   ResurrectChessProgram();
7134
7135   /* A user move restarts a paused game*/
7136   if (pausing)
7137     PauseEvent();
7138
7139   thinkOutput[0] = NULLCHAR;
7140
7141   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7142
7143   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7144     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7145     return 1;
7146   }
7147
7148   if (gameMode == BeginningOfGame) {
7149     if (appData.noChessProgram) {
7150       gameMode = EditGame;
7151       SetGameInfo();
7152     } else {
7153       char buf[MSG_SIZ];
7154       gameMode = MachinePlaysBlack;
7155       StartClocks();
7156       SetGameInfo();
7157       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7158       DisplayTitle(buf);
7159       if (first.sendName) {
7160         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7161         SendToProgram(buf, &first);
7162       }
7163       StartClocks();
7164     }
7165     ModeHighlight();
7166   }
7167
7168   /* Relay move to ICS or chess engine */
7169   if (appData.icsActive) {
7170     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7171         gameMode == IcsExamining) {
7172       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7173         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7174         SendToICS("draw ");
7175         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7176       }
7177       // also send plain move, in case ICS does not understand atomic claims
7178       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7179       ics_user_moved = 1;
7180     }
7181   } else {
7182     if (first.sendTime && (gameMode == BeginningOfGame ||
7183                            gameMode == MachinePlaysWhite ||
7184                            gameMode == MachinePlaysBlack)) {
7185       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7186     }
7187     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7188          // [HGM] book: if program might be playing, let it use book
7189         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7190         first.maybeThinking = TRUE;
7191     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7192         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7193         SendBoard(&first, currentMove+1);
7194         if(second.analyzing) {
7195             if(!second.useSetboard) SendToProgram("undo\n", &second);
7196             SendBoard(&second, currentMove+1);
7197         }
7198     } else {
7199         SendMoveToProgram(forwardMostMove-1, &first);
7200         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7201     }
7202     if (currentMove == cmailOldMove + 1) {
7203       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7204     }
7205   }
7206
7207   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7208
7209   switch (gameMode) {
7210   case EditGame:
7211     if(appData.testLegality)
7212     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7213     case MT_NONE:
7214     case MT_CHECK:
7215       break;
7216     case MT_CHECKMATE:
7217     case MT_STAINMATE:
7218       if (WhiteOnMove(currentMove)) {
7219         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7220       } else {
7221         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7222       }
7223       break;
7224     case MT_STALEMATE:
7225       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7226       break;
7227     }
7228     break;
7229
7230   case MachinePlaysBlack:
7231   case MachinePlaysWhite:
7232     /* disable certain menu options while machine is thinking */
7233     SetMachineThinkingEnables();
7234     break;
7235
7236   default:
7237     break;
7238   }
7239
7240   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7241   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7242
7243   if(bookHit) { // [HGM] book: simulate book reply
7244         static char bookMove[MSG_SIZ]; // a bit generous?
7245
7246         programStats.nodes = programStats.depth = programStats.time =
7247         programStats.score = programStats.got_only_move = 0;
7248         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7249
7250         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7251         strcat(bookMove, bookHit);
7252         HandleMachineMove(bookMove, &first);
7253   }
7254   return 1;
7255 }
7256
7257 void
7258 MarkByFEN(char *fen)
7259 {
7260         int r, f;
7261         if(!appData.markers || !appData.highlightDragging) return;
7262         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7263         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7264         while(*fen) {
7265             int s = 0;
7266             marker[r][f] = 0;
7267             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7268             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7269             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7270             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7271             if(*fen == 'T') marker[r][f++] = 0; else
7272             if(*fen == 'Y') marker[r][f++] = 1; else
7273             if(*fen == 'G') marker[r][f++] = 3; else
7274             if(*fen == 'B') marker[r][f++] = 4; else
7275             if(*fen == 'C') marker[r][f++] = 5; else
7276             if(*fen == 'M') marker[r][f++] = 6; else
7277             if(*fen == 'W') marker[r][f++] = 7; else
7278             if(*fen == 'D') marker[r][f++] = 8; else
7279             if(*fen == 'R') marker[r][f++] = 2; else {
7280                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7281               f += s; fen -= s>0;
7282             }
7283             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7284             if(r < 0) break;
7285             fen++;
7286         }
7287         DrawPosition(TRUE, NULL);
7288 }
7289
7290 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7291
7292 void
7293 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7294 {
7295     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7296     Markers *m = (Markers *) closure;
7297     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7298         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7299                          || kind == WhiteCapturesEnPassant
7300                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7301     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7302 }
7303
7304 static int hoverSavedValid;
7305
7306 void
7307 MarkTargetSquares (int clear)
7308 {
7309   int x, y, sum=0;
7310   if(clear) { // no reason to ever suppress clearing
7311     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7312     hoverSavedValid = 0;
7313     if(!sum) return; // nothing was cleared,no redraw needed
7314   } else {
7315     int capt = 0;
7316     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7317        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7318     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7319     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7320       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7321       if(capt)
7322       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7323     }
7324   }
7325   DrawPosition(FALSE, NULL);
7326 }
7327
7328 int
7329 Explode (Board board, int fromX, int fromY, int toX, int toY)
7330 {
7331     if(gameInfo.variant == VariantAtomic &&
7332        (board[toY][toX] != EmptySquare ||                     // capture?
7333         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7334                          board[fromY][fromX] == BlackPawn   )
7335       )) {
7336         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7337         return TRUE;
7338     }
7339     return FALSE;
7340 }
7341
7342 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7343
7344 int
7345 CanPromote (ChessSquare piece, int y)
7346 {
7347         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7348         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7349         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7350         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7351            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7352            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7353          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7354         return (piece == BlackPawn && y <= zone ||
7355                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7356                 piece == BlackLance && y <= zone ||
7357                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7358 }
7359
7360 void
7361 HoverEvent (int xPix, int yPix, int x, int y)
7362 {
7363         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7364         int r, f;
7365         if(!first.highlight) return;
7366         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7367         if(x == oldX && y == oldY) return; // only do something if we enter new square
7368         oldFromX = fromX; oldFromY = fromY;
7369         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7370           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7371             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7372           hoverSavedValid = 1;
7373         } else if(oldX != x || oldY != y) {
7374           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7375           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7376           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7377             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7378           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7379             char buf[MSG_SIZ];
7380             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7381             SendToProgram(buf, &first);
7382           }
7383           oldX = x; oldY = y;
7384 //        SetHighlights(fromX, fromY, x, y);
7385         }
7386 }
7387
7388 void ReportClick(char *action, int x, int y)
7389 {
7390         char buf[MSG_SIZ]; // Inform engine of what user does
7391         int r, f;
7392         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7393           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7394             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7395         if(!first.highlight || gameMode == EditPosition) return;
7396         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7397         SendToProgram(buf, &first);
7398 }
7399
7400 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7401
7402 void
7403 LeftClick (ClickType clickType, int xPix, int yPix)
7404 {
7405     int x, y;
7406     Boolean saveAnimate;
7407     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7408     char promoChoice = NULLCHAR;
7409     ChessSquare piece;
7410     static TimeMark lastClickTime, prevClickTime;
7411
7412     x = EventToSquare(xPix, BOARD_WIDTH);
7413     y = EventToSquare(yPix, BOARD_HEIGHT);
7414     if (!flipView && y >= 0) {
7415         y = BOARD_HEIGHT - 1 - y;
7416     }
7417     if (flipView && x >= 0) {
7418         x = BOARD_WIDTH - 1 - x;
7419     }
7420
7421     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7422         static int dummy;
7423         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7424         right = TRUE;
7425         return;
7426     }
7427
7428     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7429
7430     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7431
7432     if (clickType == Press) ErrorPopDown();
7433     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7434
7435     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7436         defaultPromoChoice = promoSweep;
7437         promoSweep = EmptySquare;   // terminate sweep
7438         promoDefaultAltered = TRUE;
7439         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7440     }
7441
7442     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7443         if(clickType == Release) return; // ignore upclick of click-click destination
7444         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7445         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7446         if(gameInfo.holdingsWidth &&
7447                 (WhiteOnMove(currentMove)
7448                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7449                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7450             // click in right holdings, for determining promotion piece
7451             ChessSquare p = boards[currentMove][y][x];
7452             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7453             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7454             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7455                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7456                 fromX = fromY = -1;
7457                 return;
7458             }
7459         }
7460         DrawPosition(FALSE, boards[currentMove]);
7461         return;
7462     }
7463
7464     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7465     if(clickType == Press
7466             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7467               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7468               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7469         return;
7470
7471     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7472         // could be static click on premove from-square: abort premove
7473         gotPremove = 0;
7474         ClearPremoveHighlights();
7475     }
7476
7477     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7478         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7479
7480     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7481         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7482                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7483         defaultPromoChoice = DefaultPromoChoice(side);
7484     }
7485
7486     autoQueen = appData.alwaysPromoteToQueen;
7487
7488     if (fromX == -1) {
7489       int originalY = y;
7490       gatingPiece = EmptySquare;
7491       if (clickType != Press) {
7492         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7493             DragPieceEnd(xPix, yPix); dragging = 0;
7494             DrawPosition(FALSE, NULL);
7495         }
7496         return;
7497       }
7498       doubleClick = FALSE;
7499       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7500         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7501       }
7502       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7503       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7504          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7505          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7506             /* First square */
7507             if (OKToStartUserMove(fromX, fromY)) {
7508                 second = 0;
7509                 ReportClick("lift", x, y);
7510                 MarkTargetSquares(0);
7511                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7512                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7513                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7514                     promoSweep = defaultPromoChoice;
7515                     selectFlag = 0; lastX = xPix; lastY = yPix;
7516                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7517                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7518                 }
7519                 if (appData.highlightDragging) {
7520                     SetHighlights(fromX, fromY, -1, -1);
7521                 } else {
7522                     ClearHighlights();
7523                 }
7524             } else fromX = fromY = -1;
7525             return;
7526         }
7527     }
7528 printf("to click %d,%d\n",x,y);
7529     /* fromX != -1 */
7530     if (clickType == Press && gameMode != EditPosition) {
7531         ChessSquare fromP;
7532         ChessSquare toP;
7533         int frc;
7534
7535         // ignore off-board to clicks
7536         if(y < 0 || x < 0) return;
7537
7538         /* Check if clicking again on the same color piece */
7539         fromP = boards[currentMove][fromY][fromX];
7540         toP = boards[currentMove][y][x];
7541         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7542         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7543             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7544            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7545              WhitePawn <= toP && toP <= WhiteKing &&
7546              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7547              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7548             (BlackPawn <= fromP && fromP <= BlackKing &&
7549              BlackPawn <= toP && toP <= BlackKing &&
7550              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7551              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7552             /* Clicked again on same color piece -- changed his mind */
7553             second = (x == fromX && y == fromY);
7554             killX = killY = -1;
7555             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7556                 second = FALSE; // first double-click rather than scond click
7557                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7558             }
7559             promoDefaultAltered = FALSE;
7560             MarkTargetSquares(1);
7561            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7562             if (appData.highlightDragging) {
7563                 SetHighlights(x, y, -1, -1);
7564             } else {
7565                 ClearHighlights();
7566             }
7567             if (OKToStartUserMove(x, y)) {
7568                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7569                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7570                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7571                  gatingPiece = boards[currentMove][fromY][fromX];
7572                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7573                 fromX = x;
7574                 fromY = y; dragging = 1;
7575                 if(!second) ReportClick("lift", x, y);
7576                 MarkTargetSquares(0);
7577                 DragPieceBegin(xPix, yPix, FALSE);
7578                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7579                     promoSweep = defaultPromoChoice;
7580                     selectFlag = 0; lastX = xPix; lastY = yPix;
7581                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7582                 }
7583             }
7584            }
7585            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7586            second = FALSE;
7587         }
7588         // ignore clicks on holdings
7589         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7590     }
7591 printf("A type=%d\n",clickType);
7592
7593     if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7594         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7595         return;
7596     }
7597
7598     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7599         DragPieceEnd(xPix, yPix); dragging = 0;
7600         if(clearFlag) {
7601             // a deferred attempt to click-click move an empty square on top of a piece
7602             boards[currentMove][y][x] = EmptySquare;
7603             ClearHighlights();
7604             DrawPosition(FALSE, boards[currentMove]);
7605             fromX = fromY = -1; clearFlag = 0;
7606             return;
7607         }
7608         if (appData.animateDragging) {
7609             /* Undo animation damage if any */
7610             DrawPosition(FALSE, NULL);
7611         }
7612         if (second) {
7613             /* Second up/down in same square; just abort move */
7614             second = 0;
7615             fromX = fromY = -1;
7616             gatingPiece = EmptySquare;
7617             MarkTargetSquares(1);
7618             ClearHighlights();
7619             gotPremove = 0;
7620             ClearPremoveHighlights();
7621         } else {
7622             /* First upclick in same square; start click-click mode */
7623             SetHighlights(x, y, -1, -1);
7624         }
7625         return;
7626     }
7627
7628     clearFlag = 0;
7629 printf("B\n");
7630     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7631        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7632         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7633         DisplayMessage(_("only marked squares are legal"),"");
7634         DrawPosition(TRUE, NULL);
7635         return; // ignore to-click
7636     }
7637 printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
7638     /* we now have a different from- and (possibly off-board) to-square */
7639     /* Completed move */
7640     if(!sweepSelecting) {
7641         toX = x;
7642         toY = y;
7643     }
7644
7645     piece = boards[currentMove][fromY][fromX];
7646
7647     saveAnimate = appData.animate;
7648     if (clickType == Press) {
7649         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7650         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7651             // must be Edit Position mode with empty-square selected
7652             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7653             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7654             return;
7655         }
7656         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7657             return;
7658         }
7659         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7660             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7661         } else
7662         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7663         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7664           if(appData.sweepSelect) {
7665             promoSweep = defaultPromoChoice;
7666             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7667             selectFlag = 0; lastX = xPix; lastY = yPix;
7668             Sweep(0); // Pawn that is going to promote: preview promotion piece
7669             sweepSelecting = 1;
7670             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7671             MarkTargetSquares(1);
7672           }
7673           return; // promo popup appears on up-click
7674         }
7675         /* Finish clickclick move */
7676         if (appData.animate || appData.highlightLastMove) {
7677             SetHighlights(fromX, fromY, toX, toY);
7678         } else {
7679             ClearHighlights();
7680         }
7681     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7682         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7683         if (appData.animate || appData.highlightLastMove) {
7684             SetHighlights(fromX, fromY, toX, toY);
7685         } else {
7686             ClearHighlights();
7687         }
7688     } else {
7689 #if 0
7690 // [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
7691         /* Finish drag move */
7692         if (appData.highlightLastMove) {
7693             SetHighlights(fromX, fromY, toX, toY);
7694         } else {
7695             ClearHighlights();
7696         }
7697 #endif
7698         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7699         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7700           dragging *= 2;            // flag button-less dragging if we are dragging
7701           MarkTargetSquares(1);
7702           if(x == killX && y == killY) killX = killY = -1; else {
7703             killX = x; killY = y;     //remeber this square as intermediate
7704             ReportClick("put", x, y); // and inform engine
7705             ReportClick("lift", x, y);
7706             MarkTargetSquares(0);
7707             return;
7708           }
7709         }
7710         DragPieceEnd(xPix, yPix); dragging = 0;
7711         /* Don't animate move and drag both */
7712         appData.animate = FALSE;
7713     }
7714
7715     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7716     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7717         ChessSquare piece = boards[currentMove][fromY][fromX];
7718         if(gameMode == EditPosition && piece != EmptySquare &&
7719            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7720             int n;
7721
7722             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7723                 n = PieceToNumber(piece - (int)BlackPawn);
7724                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7725                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7726                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7727             } else
7728             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7729                 n = PieceToNumber(piece);
7730                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7731                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7732                 boards[currentMove][n][BOARD_WIDTH-2]++;
7733             }
7734             boards[currentMove][fromY][fromX] = EmptySquare;
7735         }
7736         ClearHighlights();
7737         fromX = fromY = -1;
7738         MarkTargetSquares(1);
7739         DrawPosition(TRUE, boards[currentMove]);
7740         return;
7741     }
7742
7743     // off-board moves should not be highlighted
7744     if(x < 0 || y < 0) ClearHighlights();
7745     else ReportClick("put", x, y);
7746
7747     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7748
7749     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7750
7751     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7752         SetHighlights(fromX, fromY, toX, toY);
7753         MarkTargetSquares(1);
7754         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7755             // [HGM] super: promotion to captured piece selected from holdings
7756             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7757             promotionChoice = TRUE;
7758             // kludge follows to temporarily execute move on display, without promoting yet
7759             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7760             boards[currentMove][toY][toX] = p;
7761             DrawPosition(FALSE, boards[currentMove]);
7762             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7763             boards[currentMove][toY][toX] = q;
7764             DisplayMessage("Click in holdings to choose piece", "");
7765             return;
7766         }
7767         PromotionPopUp(promoChoice);
7768     } else {
7769         int oldMove = currentMove;
7770         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7771         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7772         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7773         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7774            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7775             DrawPosition(TRUE, boards[currentMove]);
7776         MarkTargetSquares(1);
7777         fromX = fromY = -1;
7778     }
7779     appData.animate = saveAnimate;
7780     if (appData.animate || appData.animateDragging) {
7781         /* Undo animation damage if needed */
7782         DrawPosition(FALSE, NULL);
7783     }
7784 }
7785
7786 int
7787 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7788 {   // front-end-free part taken out of PieceMenuPopup
7789     int whichMenu; int xSqr, ySqr;
7790
7791     if(seekGraphUp) { // [HGM] seekgraph
7792         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7793         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7794         return -2;
7795     }
7796
7797     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7798          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7799         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7800         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7801         if(action == Press)   {
7802             originalFlip = flipView;
7803             flipView = !flipView; // temporarily flip board to see game from partners perspective
7804             DrawPosition(TRUE, partnerBoard);
7805             DisplayMessage(partnerStatus, "");
7806             partnerUp = TRUE;
7807         } else if(action == Release) {
7808             flipView = originalFlip;
7809             DrawPosition(TRUE, boards[currentMove]);
7810             partnerUp = FALSE;
7811         }
7812         return -2;
7813     }
7814
7815     xSqr = EventToSquare(x, BOARD_WIDTH);
7816     ySqr = EventToSquare(y, BOARD_HEIGHT);
7817     if (action == Release) {
7818         if(pieceSweep != EmptySquare) {
7819             EditPositionMenuEvent(pieceSweep, toX, toY);
7820             pieceSweep = EmptySquare;
7821         } else UnLoadPV(); // [HGM] pv
7822     }
7823     if (action != Press) return -2; // return code to be ignored
7824     switch (gameMode) {
7825       case IcsExamining:
7826         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7827       case EditPosition:
7828         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7829         if (xSqr < 0 || ySqr < 0) return -1;
7830         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7831         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7832         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7833         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7834         NextPiece(0);
7835         return 2; // grab
7836       case IcsObserving:
7837         if(!appData.icsEngineAnalyze) return -1;
7838       case IcsPlayingWhite:
7839       case IcsPlayingBlack:
7840         if(!appData.zippyPlay) goto noZip;
7841       case AnalyzeMode:
7842       case AnalyzeFile:
7843       case MachinePlaysWhite:
7844       case MachinePlaysBlack:
7845       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7846         if (!appData.dropMenu) {
7847           LoadPV(x, y);
7848           return 2; // flag front-end to grab mouse events
7849         }
7850         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7851            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7852       case EditGame:
7853       noZip:
7854         if (xSqr < 0 || ySqr < 0) return -1;
7855         if (!appData.dropMenu || appData.testLegality &&
7856             gameInfo.variant != VariantBughouse &&
7857             gameInfo.variant != VariantCrazyhouse) return -1;
7858         whichMenu = 1; // drop menu
7859         break;
7860       default:
7861         return -1;
7862     }
7863
7864     if (((*fromX = xSqr) < 0) ||
7865         ((*fromY = ySqr) < 0)) {
7866         *fromX = *fromY = -1;
7867         return -1;
7868     }
7869     if (flipView)
7870       *fromX = BOARD_WIDTH - 1 - *fromX;
7871     else
7872       *fromY = BOARD_HEIGHT - 1 - *fromY;
7873
7874     return whichMenu;
7875 }
7876
7877 void
7878 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7879 {
7880 //    char * hint = lastHint;
7881     FrontEndProgramStats stats;
7882
7883     stats.which = cps == &first ? 0 : 1;
7884     stats.depth = cpstats->depth;
7885     stats.nodes = cpstats->nodes;
7886     stats.score = cpstats->score;
7887     stats.time = cpstats->time;
7888     stats.pv = cpstats->movelist;
7889     stats.hint = lastHint;
7890     stats.an_move_index = 0;
7891     stats.an_move_count = 0;
7892
7893     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7894         stats.hint = cpstats->move_name;
7895         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7896         stats.an_move_count = cpstats->nr_moves;
7897     }
7898
7899     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
7900
7901     SetProgramStats( &stats );
7902 }
7903
7904 void
7905 ClearEngineOutputPane (int which)
7906 {
7907     static FrontEndProgramStats dummyStats;
7908     dummyStats.which = which;
7909     dummyStats.pv = "#";
7910     SetProgramStats( &dummyStats );
7911 }
7912
7913 #define MAXPLAYERS 500
7914
7915 char *
7916 TourneyStandings (int display)
7917 {
7918     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7919     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7920     char result, *p, *names[MAXPLAYERS];
7921
7922     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7923         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7924     names[0] = p = strdup(appData.participants);
7925     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7926
7927     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7928
7929     while(result = appData.results[nr]) {
7930         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7931         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7932         wScore = bScore = 0;
7933         switch(result) {
7934           case '+': wScore = 2; break;
7935           case '-': bScore = 2; break;
7936           case '=': wScore = bScore = 1; break;
7937           case ' ':
7938           case '*': return strdup("busy"); // tourney not finished
7939         }
7940         score[w] += wScore;
7941         score[b] += bScore;
7942         games[w]++;
7943         games[b]++;
7944         nr++;
7945     }
7946     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7947     for(w=0; w<nPlayers; w++) {
7948         bScore = -1;
7949         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7950         ranking[w] = b; points[w] = bScore; score[b] = -2;
7951     }
7952     p = malloc(nPlayers*34+1);
7953     for(w=0; w<nPlayers && w<display; w++)
7954         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7955     free(names[0]);
7956     return p;
7957 }
7958
7959 void
7960 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7961 {       // count all piece types
7962         int p, f, r;
7963         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7964         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7965         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7966                 p = board[r][f];
7967                 pCnt[p]++;
7968                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7969                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7970                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7971                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7972                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7973                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7974         }
7975 }
7976
7977 int
7978 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7979 {
7980         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7981         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7982
7983         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7984         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7985         if(myPawns == 2 && nMine == 3) // KPP
7986             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7987         if(myPawns == 1 && nMine == 2) // KP
7988             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7989         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7990             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7991         if(myPawns) return FALSE;
7992         if(pCnt[WhiteRook+side])
7993             return pCnt[BlackRook-side] ||
7994                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7995                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7996                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7997         if(pCnt[WhiteCannon+side]) {
7998             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7999             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8000         }
8001         if(pCnt[WhiteKnight+side])
8002             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8003         return FALSE;
8004 }
8005
8006 int
8007 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8008 {
8009         VariantClass v = gameInfo.variant;
8010
8011         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8012         if(v == VariantShatranj) return TRUE; // always winnable through baring
8013         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8014         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8015
8016         if(v == VariantXiangqi) {
8017                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8018
8019                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8020                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8021                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8022                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8023                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8024                 if(stale) // we have at least one last-rank P plus perhaps C
8025                     return majors // KPKX
8026                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8027                 else // KCA*E*
8028                     return pCnt[WhiteFerz+side] // KCAK
8029                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8030                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8031                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8032
8033         } else if(v == VariantKnightmate) {
8034                 if(nMine == 1) return FALSE;
8035                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8036         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8037                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8038
8039                 if(nMine == 1) return FALSE; // bare King
8040                 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
8041                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8042                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8043                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8044                 if(pCnt[WhiteKnight+side])
8045                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8046                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8047                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8048                 if(nBishops)
8049                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8050                 if(pCnt[WhiteAlfil+side])
8051                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8052                 if(pCnt[WhiteWazir+side])
8053                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8054         }
8055
8056         return TRUE;
8057 }
8058
8059 int
8060 CompareWithRights (Board b1, Board b2)
8061 {
8062     int rights = 0;
8063     if(!CompareBoards(b1, b2)) return FALSE;
8064     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8065     /* compare castling rights */
8066     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8067            rights++; /* King lost rights, while rook still had them */
8068     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8069         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8070            rights++; /* but at least one rook lost them */
8071     }
8072     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8073            rights++;
8074     if( b1[CASTLING][5] != NoRights ) {
8075         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8076            rights++;
8077     }
8078     return rights == 0;
8079 }
8080
8081 int
8082 Adjudicate (ChessProgramState *cps)
8083 {       // [HGM] some adjudications useful with buggy engines
8084         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8085         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8086         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8087         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8088         int k, drop, count = 0; static int bare = 1;
8089         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8090         Boolean canAdjudicate = !appData.icsActive;
8091
8092         // most tests only when we understand the game, i.e. legality-checking on
8093             if( appData.testLegality )
8094             {   /* [HGM] Some more adjudications for obstinate engines */
8095                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8096                 static int moveCount = 6;
8097                 ChessMove result;
8098                 char *reason = NULL;
8099
8100                 /* Count what is on board. */
8101                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8102
8103                 /* Some material-based adjudications that have to be made before stalemate test */
8104                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8105                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8106                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8107                      if(canAdjudicate && appData.checkMates) {
8108                          if(engineOpponent)
8109                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8110                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8111                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8112                          return 1;
8113                      }
8114                 }
8115
8116                 /* Bare King in Shatranj (loses) or Losers (wins) */
8117                 if( nrW == 1 || nrB == 1) {
8118                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8119                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8120                      if(canAdjudicate && appData.checkMates) {
8121                          if(engineOpponent)
8122                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8123                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8124                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8125                          return 1;
8126                      }
8127                   } else
8128                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8129                   {    /* bare King */
8130                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8131                         if(canAdjudicate && appData.checkMates) {
8132                             /* but only adjudicate if adjudication enabled */
8133                             if(engineOpponent)
8134                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8135                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8136                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8137                             return 1;
8138                         }
8139                   }
8140                 } else bare = 1;
8141
8142
8143             // don't wait for engine to announce game end if we can judge ourselves
8144             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8145               case MT_CHECK:
8146                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8147                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8148                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8149                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8150                             checkCnt++;
8151                         if(checkCnt >= 2) {
8152                             reason = "Xboard adjudication: 3rd check";
8153                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8154                             break;
8155                         }
8156                     }
8157                 }
8158               case MT_NONE:
8159               default:
8160                 break;
8161               case MT_STEALMATE:
8162               case MT_STALEMATE:
8163               case MT_STAINMATE:
8164                 reason = "Xboard adjudication: Stalemate";
8165                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8166                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8167                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8168                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8169                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8170                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8171                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8172                                                                         EP_CHECKMATE : EP_WINS);
8173                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8174                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8175                 }
8176                 break;
8177               case MT_CHECKMATE:
8178                 reason = "Xboard adjudication: Checkmate";
8179                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8180                 if(gameInfo.variant == VariantShogi) {
8181                     if(forwardMostMove > backwardMostMove
8182                        && moveList[forwardMostMove-1][1] == '@'
8183                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8184                         reason = "XBoard adjudication: pawn-drop mate";
8185                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8186                     }
8187                 }
8188                 break;
8189             }
8190
8191                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8192                     case EP_STALEMATE:
8193                         result = GameIsDrawn; break;
8194                     case EP_CHECKMATE:
8195                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8196                     case EP_WINS:
8197                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8198                     default:
8199                         result = EndOfFile;
8200                 }
8201                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8202                     if(engineOpponent)
8203                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8204                     GameEnds( result, reason, GE_XBOARD );
8205                     return 1;
8206                 }
8207
8208                 /* Next absolutely insufficient mating material. */
8209                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8210                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8211                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8212
8213                      /* always flag draws, for judging claims */
8214                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8215
8216                      if(canAdjudicate && appData.materialDraws) {
8217                          /* but only adjudicate them if adjudication enabled */
8218                          if(engineOpponent) {
8219                            SendToProgram("force\n", engineOpponent); // suppress reply
8220                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8221                          }
8222                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8223                          return 1;
8224                      }
8225                 }
8226
8227                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8228                 if(gameInfo.variant == VariantXiangqi ?
8229                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8230                  : nrW + nrB == 4 &&
8231                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8232                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8233                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8234                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8235                    ) ) {
8236                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8237                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8238                           if(engineOpponent) {
8239                             SendToProgram("force\n", engineOpponent); // suppress reply
8240                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8241                           }
8242                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8243                           return 1;
8244                      }
8245                 } else moveCount = 6;
8246             }
8247
8248         // Repetition draws and 50-move rule can be applied independently of legality testing
8249
8250                 /* Check for rep-draws */
8251                 count = 0;
8252                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8253                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8254                 for(k = forwardMostMove-2;
8255                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8256                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8257                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8258                     k-=2)
8259                 {   int rights=0;
8260                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8261                         /* compare castling rights */
8262                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8263                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8264                                 rights++; /* King lost rights, while rook still had them */
8265                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8266                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8267                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8268                                    rights++; /* but at least one rook lost them */
8269                         }
8270                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8271                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8272                                 rights++;
8273                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8274                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8275                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8276                                    rights++;
8277                         }
8278                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8279                             && appData.drawRepeats > 1) {
8280                              /* adjudicate after user-specified nr of repeats */
8281                              int result = GameIsDrawn;
8282                              char *details = "XBoard adjudication: repetition draw";
8283                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8284                                 // [HGM] xiangqi: check for forbidden perpetuals
8285                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8286                                 for(m=forwardMostMove; m>k; m-=2) {
8287                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8288                                         ourPerpetual = 0; // the current mover did not always check
8289                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8290                                         hisPerpetual = 0; // the opponent did not always check
8291                                 }
8292                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8293                                                                         ourPerpetual, hisPerpetual);
8294                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8295                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8296                                     details = "Xboard adjudication: perpetual checking";
8297                                 } else
8298                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8299                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8300                                 } else
8301                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8302                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8303                                         result = BlackWins;
8304                                         details = "Xboard adjudication: repetition";
8305                                     }
8306                                 } else // it must be XQ
8307                                 // Now check for perpetual chases
8308                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8309                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8310                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8311                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8312                                         static char resdet[MSG_SIZ];
8313                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8314                                         details = resdet;
8315                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8316                                     } else
8317                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8318                                         break; // Abort repetition-checking loop.
8319                                 }
8320                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8321                              }
8322                              if(engineOpponent) {
8323                                SendToProgram("force\n", engineOpponent); // suppress reply
8324                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8325                              }
8326                              GameEnds( result, details, GE_XBOARD );
8327                              return 1;
8328                         }
8329                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8330                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8331                     }
8332                 }
8333
8334                 /* Now we test for 50-move draws. Determine ply count */
8335                 count = forwardMostMove;
8336                 /* look for last irreversble move */
8337                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8338                     count--;
8339                 /* if we hit starting position, add initial plies */
8340                 if( count == backwardMostMove )
8341                     count -= initialRulePlies;
8342                 count = forwardMostMove - count;
8343                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8344                         // adjust reversible move counter for checks in Xiangqi
8345                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8346                         if(i < backwardMostMove) i = backwardMostMove;
8347                         while(i <= forwardMostMove) {
8348                                 lastCheck = inCheck; // check evasion does not count
8349                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8350                                 if(inCheck || lastCheck) count--; // check does not count
8351                                 i++;
8352                         }
8353                 }
8354                 if( count >= 100)
8355                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8356                          /* this is used to judge if draw claims are legal */
8357                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8358                          if(engineOpponent) {
8359                            SendToProgram("force\n", engineOpponent); // suppress reply
8360                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8361                          }
8362                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8363                          return 1;
8364                 }
8365
8366                 /* if draw offer is pending, treat it as a draw claim
8367                  * when draw condition present, to allow engines a way to
8368                  * claim draws before making their move to avoid a race
8369                  * condition occurring after their move
8370                  */
8371                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8372                          char *p = NULL;
8373                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8374                              p = "Draw claim: 50-move rule";
8375                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8376                              p = "Draw claim: 3-fold repetition";
8377                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8378                              p = "Draw claim: insufficient mating material";
8379                          if( p != NULL && canAdjudicate) {
8380                              if(engineOpponent) {
8381                                SendToProgram("force\n", engineOpponent); // suppress reply
8382                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8383                              }
8384                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8385                              return 1;
8386                          }
8387                 }
8388
8389                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8390                     if(engineOpponent) {
8391                       SendToProgram("force\n", engineOpponent); // suppress reply
8392                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8393                     }
8394                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8395                     return 1;
8396                 }
8397         return 0;
8398 }
8399
8400 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8401 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8402 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8403
8404 static int
8405 BitbaseProbe ()
8406 {
8407     int pieces[10], squares[10], cnt=0, r, f, res;
8408     static int loaded;
8409     static PPROBE_EGBB probeBB;
8410     if(!appData.testLegality) return 10;
8411     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8412     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8413     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8414     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8415         ChessSquare piece = boards[forwardMostMove][r][f];
8416         int black = (piece >= BlackPawn);
8417         int type = piece - black*BlackPawn;
8418         if(piece == EmptySquare) continue;
8419         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8420         if(type == WhiteKing) type = WhiteQueen + 1;
8421         type = egbbCode[type];
8422         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8423         pieces[cnt] = type + black*6;
8424         if(++cnt > 5) return 11;
8425     }
8426     pieces[cnt] = squares[cnt] = 0;
8427     // probe EGBB
8428     if(loaded == 2) return 13; // loading failed before
8429     if(loaded == 0) {
8430         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8431         HMODULE lib;
8432         PLOAD_EGBB loadBB;
8433         loaded = 2; // prepare for failure
8434         if(!path) return 13; // no egbb installed
8435         strncpy(buf, path + 8, MSG_SIZ);
8436         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8437         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8438         lib = LoadLibrary(buf);
8439         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8440         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8441         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8442         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8443         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8444         loaded = 1; // success!
8445     }
8446     res = probeBB(forwardMostMove & 1, pieces, squares);
8447     return res > 0 ? 1 : res < 0 ? -1 : 0;
8448 }
8449
8450 char *
8451 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8452 {   // [HGM] book: this routine intercepts moves to simulate book replies
8453     char *bookHit = NULL;
8454
8455     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8456         char buf[MSG_SIZ];
8457         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8458         SendToProgram(buf, cps);
8459     }
8460     //first determine if the incoming move brings opponent into his book
8461     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8462         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8463     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8464     if(bookHit != NULL && !cps->bookSuspend) {
8465         // make sure opponent is not going to reply after receiving move to book position
8466         SendToProgram("force\n", cps);
8467         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8468     }
8469     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8470     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8471     // now arrange restart after book miss
8472     if(bookHit) {
8473         // after a book hit we never send 'go', and the code after the call to this routine
8474         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8475         char buf[MSG_SIZ], *move = bookHit;
8476         if(cps->useSAN) {
8477             int fromX, fromY, toX, toY;
8478             char promoChar;
8479             ChessMove moveType;
8480             move = buf + 30;
8481             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8482                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8483                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8484                                     PosFlags(forwardMostMove),
8485                                     fromY, fromX, toY, toX, promoChar, move);
8486             } else {
8487                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8488                 bookHit = NULL;
8489             }
8490         }
8491         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8492         SendToProgram(buf, cps);
8493         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8494     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8495         SendToProgram("go\n", cps);
8496         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8497     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8498         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8499             SendToProgram("go\n", cps);
8500         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8501     }
8502     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8503 }
8504
8505 int
8506 LoadError (char *errmess, ChessProgramState *cps)
8507 {   // unloads engine and switches back to -ncp mode if it was first
8508     if(cps->initDone) return FALSE;
8509     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8510     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8511     cps->pr = NoProc;
8512     if(cps == &first) {
8513         appData.noChessProgram = TRUE;
8514         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8515         gameMode = BeginningOfGame; ModeHighlight();
8516         SetNCPMode();
8517     }
8518     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8519     DisplayMessage("", ""); // erase waiting message
8520     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8521     return TRUE;
8522 }
8523
8524 char *savedMessage;
8525 ChessProgramState *savedState;
8526 void
8527 DeferredBookMove (void)
8528 {
8529         if(savedState->lastPing != savedState->lastPong)
8530                     ScheduleDelayedEvent(DeferredBookMove, 10);
8531         else
8532         HandleMachineMove(savedMessage, savedState);
8533 }
8534
8535 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8536 static ChessProgramState *stalledEngine;
8537 static char stashedInputMove[MSG_SIZ];
8538
8539 void
8540 HandleMachineMove (char *message, ChessProgramState *cps)
8541 {
8542     static char firstLeg[20];
8543     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8544     char realname[MSG_SIZ];
8545     int fromX, fromY, toX, toY;
8546     ChessMove moveType;
8547     char promoChar, roar;
8548     char *p, *pv=buf1;
8549     int machineWhite, oldError;
8550     char *bookHit;
8551
8552     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8553         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8554         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8555             DisplayError(_("Invalid pairing from pairing engine"), 0);
8556             return;
8557         }
8558         pairingReceived = 1;
8559         NextMatchGame();
8560         return; // Skim the pairing messages here.
8561     }
8562
8563     oldError = cps->userError; cps->userError = 0;
8564
8565 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8566     /*
8567      * Kludge to ignore BEL characters
8568      */
8569     while (*message == '\007') message++;
8570
8571     /*
8572      * [HGM] engine debug message: ignore lines starting with '#' character
8573      */
8574     if(cps->debug && *message == '#') return;
8575
8576     /*
8577      * Look for book output
8578      */
8579     if (cps == &first && bookRequested) {
8580         if (message[0] == '\t' || message[0] == ' ') {
8581             /* Part of the book output is here; append it */
8582             strcat(bookOutput, message);
8583             strcat(bookOutput, "  \n");
8584             return;
8585         } else if (bookOutput[0] != NULLCHAR) {
8586             /* All of book output has arrived; display it */
8587             char *p = bookOutput;
8588             while (*p != NULLCHAR) {
8589                 if (*p == '\t') *p = ' ';
8590                 p++;
8591             }
8592             DisplayInformation(bookOutput);
8593             bookRequested = FALSE;
8594             /* Fall through to parse the current output */
8595         }
8596     }
8597
8598     /*
8599      * Look for machine move.
8600      */
8601     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8602         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8603     {
8604         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8605             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8606             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8607             stalledEngine = cps;
8608             if(appData.ponderNextMove) { // bring opponent out of ponder
8609                 if(gameMode == TwoMachinesPlay) {
8610                     if(cps->other->pause)
8611                         PauseEngine(cps->other);
8612                     else
8613                         SendToProgram("easy\n", cps->other);
8614                 }
8615             }
8616             StopClocks();
8617             return;
8618         }
8619
8620         /* This method is only useful on engines that support ping */
8621         if (cps->lastPing != cps->lastPong) {
8622           if (gameMode == BeginningOfGame) {
8623             /* Extra move from before last new; ignore */
8624             if (appData.debugMode) {
8625                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8626             }
8627           } else {
8628             if (appData.debugMode) {
8629                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8630                         cps->which, gameMode);
8631             }
8632
8633             SendToProgram("undo\n", cps);
8634           }
8635           return;
8636         }
8637
8638         switch (gameMode) {
8639           case BeginningOfGame:
8640             /* Extra move from before last reset; ignore */
8641             if (appData.debugMode) {
8642                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8643             }
8644             return;
8645
8646           case EndOfGame:
8647           case IcsIdle:
8648           default:
8649             /* Extra move after we tried to stop.  The mode test is
8650                not a reliable way of detecting this problem, but it's
8651                the best we can do on engines that don't support ping.
8652             */
8653             if (appData.debugMode) {
8654                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8655                         cps->which, gameMode);
8656             }
8657             SendToProgram("undo\n", cps);
8658             return;
8659
8660           case MachinePlaysWhite:
8661           case IcsPlayingWhite:
8662             machineWhite = TRUE;
8663             break;
8664
8665           case MachinePlaysBlack:
8666           case IcsPlayingBlack:
8667             machineWhite = FALSE;
8668             break;
8669
8670           case TwoMachinesPlay:
8671             machineWhite = (cps->twoMachinesColor[0] == 'w');
8672             break;
8673         }
8674         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8675             if (appData.debugMode) {
8676                 fprintf(debugFP,
8677                         "Ignoring move out of turn by %s, gameMode %d"
8678                         ", forwardMost %d\n",
8679                         cps->which, gameMode, forwardMostMove);
8680             }
8681             return;
8682         }
8683
8684         if(cps->alphaRank) AlphaRank(machineMove, 4);
8685
8686         // [HGM] lion: (some very limited) support for Alien protocol
8687         killX = killY = -1;
8688         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8689             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8690             return;
8691         } else if(firstLeg[0]) { // there was a previous leg;
8692             // only support case where same piece makes two step
8693             char buf[20], *p = machineMove+1, *q = buf+1, f;
8694             safeStrCpy(buf, machineMove, 20);
8695             while(isdigit(*q)) q++; // find start of to-square
8696             safeStrCpy(machineMove, firstLeg, 20);
8697             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8698             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8699             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8700             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8701             firstLeg[0] = NULLCHAR;
8702         }
8703
8704         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8705                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8706             /* Machine move could not be parsed; ignore it. */
8707           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8708                     machineMove, _(cps->which));
8709             DisplayMoveError(buf1);
8710             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8711                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8712             if (gameMode == TwoMachinesPlay) {
8713               GameEnds(machineWhite ? BlackWins : WhiteWins,
8714                        buf1, GE_XBOARD);
8715             }
8716             return;
8717         }
8718
8719         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8720         /* So we have to redo legality test with true e.p. status here,  */
8721         /* to make sure an illegal e.p. capture does not slip through,   */
8722         /* to cause a forfeit on a justified illegal-move complaint      */
8723         /* of the opponent.                                              */
8724         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8725            ChessMove moveType;
8726            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8727                              fromY, fromX, toY, toX, promoChar);
8728             if(moveType == IllegalMove) {
8729               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8730                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8731                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8732                            buf1, GE_XBOARD);
8733                 return;
8734            } else if(!appData.fischerCastling)
8735            /* [HGM] Kludge to handle engines that send FRC-style castling
8736               when they shouldn't (like TSCP-Gothic) */
8737            switch(moveType) {
8738              case WhiteASideCastleFR:
8739              case BlackASideCastleFR:
8740                toX+=2;
8741                currentMoveString[2]++;
8742                break;
8743              case WhiteHSideCastleFR:
8744              case BlackHSideCastleFR:
8745                toX--;
8746                currentMoveString[2]--;
8747                break;
8748              default: ; // nothing to do, but suppresses warning of pedantic compilers
8749            }
8750         }
8751         hintRequested = FALSE;
8752         lastHint[0] = NULLCHAR;
8753         bookRequested = FALSE;
8754         /* Program may be pondering now */
8755         cps->maybeThinking = TRUE;
8756         if (cps->sendTime == 2) cps->sendTime = 1;
8757         if (cps->offeredDraw) cps->offeredDraw--;
8758
8759         /* [AS] Save move info*/
8760         pvInfoList[ forwardMostMove ].score = programStats.score;
8761         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8762         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8763
8764         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8765
8766         /* Test suites abort the 'game' after one move */
8767         if(*appData.finger) {
8768            static FILE *f;
8769            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8770            if(!f) f = fopen(appData.finger, "w");
8771            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8772            else { DisplayFatalError("Bad output file", errno, 0); return; }
8773            free(fen);
8774            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8775         }
8776
8777         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8778         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8779             int count = 0;
8780
8781             while( count < adjudicateLossPlies ) {
8782                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8783
8784                 if( count & 1 ) {
8785                     score = -score; /* Flip score for winning side */
8786                 }
8787
8788                 if( score > appData.adjudicateLossThreshold ) {
8789                     break;
8790                 }
8791
8792                 count++;
8793             }
8794
8795             if( count >= adjudicateLossPlies ) {
8796                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8797
8798                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8799                     "Xboard adjudication",
8800                     GE_XBOARD );
8801
8802                 return;
8803             }
8804         }
8805
8806         if(Adjudicate(cps)) {
8807             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8808             return; // [HGM] adjudicate: for all automatic game ends
8809         }
8810
8811 #if ZIPPY
8812         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8813             first.initDone) {
8814           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8815                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8816                 SendToICS("draw ");
8817                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8818           }
8819           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8820           ics_user_moved = 1;
8821           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8822                 char buf[3*MSG_SIZ];
8823
8824                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8825                         programStats.score / 100.,
8826                         programStats.depth,
8827                         programStats.time / 100.,
8828                         (unsigned int)programStats.nodes,
8829                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8830                         programStats.movelist);
8831                 SendToICS(buf);
8832           }
8833         }
8834 #endif
8835
8836         /* [AS] Clear stats for next move */
8837         ClearProgramStats();
8838         thinkOutput[0] = NULLCHAR;
8839         hiddenThinkOutputState = 0;
8840
8841         bookHit = NULL;
8842         if (gameMode == TwoMachinesPlay) {
8843             /* [HGM] relaying draw offers moved to after reception of move */
8844             /* and interpreting offer as claim if it brings draw condition */
8845             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8846                 SendToProgram("draw\n", cps->other);
8847             }
8848             if (cps->other->sendTime) {
8849                 SendTimeRemaining(cps->other,
8850                                   cps->other->twoMachinesColor[0] == 'w');
8851             }
8852             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8853             if (firstMove && !bookHit) {
8854                 firstMove = FALSE;
8855                 if (cps->other->useColors) {
8856                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8857                 }
8858                 SendToProgram("go\n", cps->other);
8859             }
8860             cps->other->maybeThinking = TRUE;
8861         }
8862
8863         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8864
8865         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8866
8867         if (!pausing && appData.ringBellAfterMoves) {
8868             if(!roar) RingBell();
8869         }
8870
8871         /*
8872          * Reenable menu items that were disabled while
8873          * machine was thinking
8874          */
8875         if (gameMode != TwoMachinesPlay)
8876             SetUserThinkingEnables();
8877
8878         // [HGM] book: after book hit opponent has received move and is now in force mode
8879         // force the book reply into it, and then fake that it outputted this move by jumping
8880         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8881         if(bookHit) {
8882                 static char bookMove[MSG_SIZ]; // a bit generous?
8883
8884                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8885                 strcat(bookMove, bookHit);
8886                 message = bookMove;
8887                 cps = cps->other;
8888                 programStats.nodes = programStats.depth = programStats.time =
8889                 programStats.score = programStats.got_only_move = 0;
8890                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8891
8892                 if(cps->lastPing != cps->lastPong) {
8893                     savedMessage = message; // args for deferred call
8894                     savedState = cps;
8895                     ScheduleDelayedEvent(DeferredBookMove, 10);
8896                     return;
8897                 }
8898                 goto FakeBookMove;
8899         }
8900
8901         return;
8902     }
8903
8904     /* Set special modes for chess engines.  Later something general
8905      *  could be added here; for now there is just one kludge feature,
8906      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8907      *  when "xboard" is given as an interactive command.
8908      */
8909     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8910         cps->useSigint = FALSE;
8911         cps->useSigterm = FALSE;
8912     }
8913     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8914       ParseFeatures(message+8, cps);
8915       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8916     }
8917
8918     if (!strncmp(message, "setup ", 6) && 
8919         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8920           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8921                                         ) { // [HGM] allow first engine to define opening position
8922       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8923       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8924       *buf = NULLCHAR;
8925       if(sscanf(message, "setup (%s", buf) == 1) {
8926         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8927         ASSIGN(appData.pieceToCharTable, buf);
8928       }
8929       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8930       if(dummy >= 3) {
8931         while(message[s] && message[s++] != ' ');
8932         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8933            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8934             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8935             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8936           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8937           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8938           startedFromSetupPosition = FALSE;
8939         }
8940       }
8941       if(startedFromSetupPosition) return;
8942       ParseFEN(boards[0], &dummy, message+s, FALSE);
8943       DrawPosition(TRUE, boards[0]);
8944       CopyBoard(initialPosition, boards[0]);
8945       startedFromSetupPosition = TRUE;
8946       return;
8947     }
8948     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8949       ChessSquare piece = WhitePawn;
8950       char *p=buf2;
8951       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8952       piece += CharToPiece(*p) - WhitePawn;
8953       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8954       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8955       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8956       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8957       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8958       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8959                                                && gameInfo.variant != VariantGreat
8960                                                && gameInfo.variant != VariantFairy    ) return;
8961       if(piece < EmptySquare) {
8962         pieceDefs = TRUE;
8963         ASSIGN(pieceDesc[piece], buf1);
8964         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8965       }
8966       return;
8967     }
8968     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8969      * want this, I was asked to put it in, and obliged.
8970      */
8971     if (!strncmp(message, "setboard ", 9)) {
8972         Board initial_position;
8973
8974         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8975
8976         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8977             DisplayError(_("Bad FEN received from engine"), 0);
8978             return ;
8979         } else {
8980            Reset(TRUE, FALSE);
8981            CopyBoard(boards[0], initial_position);
8982            initialRulePlies = FENrulePlies;
8983            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8984            else gameMode = MachinePlaysBlack;
8985            DrawPosition(FALSE, boards[currentMove]);
8986         }
8987         return;
8988     }
8989
8990     /*
8991      * Look for communication commands
8992      */
8993     if (!strncmp(message, "telluser ", 9)) {
8994         if(message[9] == '\\' && message[10] == '\\')
8995             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8996         PlayTellSound();
8997         DisplayNote(message + 9);
8998         return;
8999     }
9000     if (!strncmp(message, "tellusererror ", 14)) {
9001         cps->userError = 1;
9002         if(message[14] == '\\' && message[15] == '\\')
9003             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9004         PlayTellSound();
9005         DisplayError(message + 14, 0);
9006         return;
9007     }
9008     if (!strncmp(message, "tellopponent ", 13)) {
9009       if (appData.icsActive) {
9010         if (loggedOn) {
9011           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9012           SendToICS(buf1);
9013         }
9014       } else {
9015         DisplayNote(message + 13);
9016       }
9017       return;
9018     }
9019     if (!strncmp(message, "tellothers ", 11)) {
9020       if (appData.icsActive) {
9021         if (loggedOn) {
9022           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9023           SendToICS(buf1);
9024         }
9025       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9026       return;
9027     }
9028     if (!strncmp(message, "tellall ", 8)) {
9029       if (appData.icsActive) {
9030         if (loggedOn) {
9031           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9032           SendToICS(buf1);
9033         }
9034       } else {
9035         DisplayNote(message + 8);
9036       }
9037       return;
9038     }
9039     if (strncmp(message, "warning", 7) == 0) {
9040         /* Undocumented feature, use tellusererror in new code */
9041         DisplayError(message, 0);
9042         return;
9043     }
9044     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9045         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9046         strcat(realname, " query");
9047         AskQuestion(realname, buf2, buf1, cps->pr);
9048         return;
9049     }
9050     /* Commands from the engine directly to ICS.  We don't allow these to be
9051      *  sent until we are logged on. Crafty kibitzes have been known to
9052      *  interfere with the login process.
9053      */
9054     if (loggedOn) {
9055         if (!strncmp(message, "tellics ", 8)) {
9056             SendToICS(message + 8);
9057             SendToICS("\n");
9058             return;
9059         }
9060         if (!strncmp(message, "tellicsnoalias ", 15)) {
9061             SendToICS(ics_prefix);
9062             SendToICS(message + 15);
9063             SendToICS("\n");
9064             return;
9065         }
9066         /* The following are for backward compatibility only */
9067         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9068             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9069             SendToICS(ics_prefix);
9070             SendToICS(message);
9071             SendToICS("\n");
9072             return;
9073         }
9074     }
9075     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9076         if(initPing == cps->lastPong) {
9077             if(gameInfo.variant == VariantUnknown) {
9078                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9079                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9080                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9081             }
9082             initPing = -1;
9083         }
9084         return;
9085     }
9086     if(!strncmp(message, "highlight ", 10)) {
9087         if(appData.testLegality && appData.markers) return;
9088         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9089         return;
9090     }
9091     if(!strncmp(message, "click ", 6)) {
9092         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9093         if(appData.testLegality || !appData.oneClick) return;
9094         sscanf(message+6, "%c%d%c", &f, &y, &c);
9095         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9096         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9097         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9098         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9099         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9100         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9101             LeftClick(Release, lastLeftX, lastLeftY);
9102         controlKey  = (c == ',');
9103         LeftClick(Press, x, y);
9104         LeftClick(Release, x, y);
9105         first.highlight = f;
9106         return;
9107     }
9108     /*
9109      * If the move is illegal, cancel it and redraw the board.
9110      * Also deal with other error cases.  Matching is rather loose
9111      * here to accommodate engines written before the spec.
9112      */
9113     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9114         strncmp(message, "Error", 5) == 0) {
9115         if (StrStr(message, "name") ||
9116             StrStr(message, "rating") || StrStr(message, "?") ||
9117             StrStr(message, "result") || StrStr(message, "board") ||
9118             StrStr(message, "bk") || StrStr(message, "computer") ||
9119             StrStr(message, "variant") || StrStr(message, "hint") ||
9120             StrStr(message, "random") || StrStr(message, "depth") ||
9121             StrStr(message, "accepted")) {
9122             return;
9123         }
9124         if (StrStr(message, "protover")) {
9125           /* Program is responding to input, so it's apparently done
9126              initializing, and this error message indicates it is
9127              protocol version 1.  So we don't need to wait any longer
9128              for it to initialize and send feature commands. */
9129           FeatureDone(cps, 1);
9130           cps->protocolVersion = 1;
9131           return;
9132         }
9133         cps->maybeThinking = FALSE;
9134
9135         if (StrStr(message, "draw")) {
9136             /* Program doesn't have "draw" command */
9137             cps->sendDrawOffers = 0;
9138             return;
9139         }
9140         if (cps->sendTime != 1 &&
9141             (StrStr(message, "time") || StrStr(message, "otim"))) {
9142           /* Program apparently doesn't have "time" or "otim" command */
9143           cps->sendTime = 0;
9144           return;
9145         }
9146         if (StrStr(message, "analyze")) {
9147             cps->analysisSupport = FALSE;
9148             cps->analyzing = FALSE;
9149 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9150             EditGameEvent(); // [HGM] try to preserve loaded game
9151             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9152             DisplayError(buf2, 0);
9153             return;
9154         }
9155         if (StrStr(message, "(no matching move)st")) {
9156           /* Special kludge for GNU Chess 4 only */
9157           cps->stKludge = TRUE;
9158           SendTimeControl(cps, movesPerSession, timeControl,
9159                           timeIncrement, appData.searchDepth,
9160                           searchTime);
9161           return;
9162         }
9163         if (StrStr(message, "(no matching move)sd")) {
9164           /* Special kludge for GNU Chess 4 only */
9165           cps->sdKludge = TRUE;
9166           SendTimeControl(cps, movesPerSession, timeControl,
9167                           timeIncrement, appData.searchDepth,
9168                           searchTime);
9169           return;
9170         }
9171         if (!StrStr(message, "llegal")) {
9172             return;
9173         }
9174         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9175             gameMode == IcsIdle) return;
9176         if (forwardMostMove <= backwardMostMove) return;
9177         if (pausing) PauseEvent();
9178       if(appData.forceIllegal) {
9179             // [HGM] illegal: machine refused move; force position after move into it
9180           SendToProgram("force\n", cps);
9181           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9182                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9183                 // when black is to move, while there might be nothing on a2 or black
9184                 // might already have the move. So send the board as if white has the move.
9185                 // But first we must change the stm of the engine, as it refused the last move
9186                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9187                 if(WhiteOnMove(forwardMostMove)) {
9188                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9189                     SendBoard(cps, forwardMostMove); // kludgeless board
9190                 } else {
9191                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9192                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9193                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9194                 }
9195           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9196             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9197                  gameMode == TwoMachinesPlay)
9198               SendToProgram("go\n", cps);
9199             return;
9200       } else
9201         if (gameMode == PlayFromGameFile) {
9202             /* Stop reading this game file */
9203             gameMode = EditGame;
9204             ModeHighlight();
9205         }
9206         /* [HGM] illegal-move claim should forfeit game when Xboard */
9207         /* only passes fully legal moves                            */
9208         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9209             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9210                                 "False illegal-move claim", GE_XBOARD );
9211             return; // do not take back move we tested as valid
9212         }
9213         currentMove = forwardMostMove-1;
9214         DisplayMove(currentMove-1); /* before DisplayMoveError */
9215         SwitchClocks(forwardMostMove-1); // [HGM] race
9216         DisplayBothClocks();
9217         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9218                 parseList[currentMove], _(cps->which));
9219         DisplayMoveError(buf1);
9220         DrawPosition(FALSE, boards[currentMove]);
9221
9222         SetUserThinkingEnables();
9223         return;
9224     }
9225     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9226         /* Program has a broken "time" command that
9227            outputs a string not ending in newline.
9228            Don't use it. */
9229         cps->sendTime = 0;
9230     }
9231     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9232         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9233             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9234     }
9235
9236     /*
9237      * If chess program startup fails, exit with an error message.
9238      * Attempts to recover here are futile. [HGM] Well, we try anyway
9239      */
9240     if ((StrStr(message, "unknown host") != NULL)
9241         || (StrStr(message, "No remote directory") != NULL)
9242         || (StrStr(message, "not found") != NULL)
9243         || (StrStr(message, "No such file") != NULL)
9244         || (StrStr(message, "can't alloc") != NULL)
9245         || (StrStr(message, "Permission denied") != NULL)) {
9246
9247         cps->maybeThinking = FALSE;
9248         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9249                 _(cps->which), cps->program, cps->host, message);
9250         RemoveInputSource(cps->isr);
9251         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9252             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9253             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9254         }
9255         return;
9256     }
9257
9258     /*
9259      * Look for hint output
9260      */
9261     if (sscanf(message, "Hint: %s", buf1) == 1) {
9262         if (cps == &first && hintRequested) {
9263             hintRequested = FALSE;
9264             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9265                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9266                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9267                                     PosFlags(forwardMostMove),
9268                                     fromY, fromX, toY, toX, promoChar, buf1);
9269                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9270                 DisplayInformation(buf2);
9271             } else {
9272                 /* Hint move could not be parsed!? */
9273               snprintf(buf2, sizeof(buf2),
9274                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9275                         buf1, _(cps->which));
9276                 DisplayError(buf2, 0);
9277             }
9278         } else {
9279           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9280         }
9281         return;
9282     }
9283
9284     /*
9285      * Ignore other messages if game is not in progress
9286      */
9287     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9288         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9289
9290     /*
9291      * look for win, lose, draw, or draw offer
9292      */
9293     if (strncmp(message, "1-0", 3) == 0) {
9294         char *p, *q, *r = "";
9295         p = strchr(message, '{');
9296         if (p) {
9297             q = strchr(p, '}');
9298             if (q) {
9299                 *q = NULLCHAR;
9300                 r = p + 1;
9301             }
9302         }
9303         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9304         return;
9305     } else if (strncmp(message, "0-1", 3) == 0) {
9306         char *p, *q, *r = "";
9307         p = strchr(message, '{');
9308         if (p) {
9309             q = strchr(p, '}');
9310             if (q) {
9311                 *q = NULLCHAR;
9312                 r = p + 1;
9313             }
9314         }
9315         /* Kludge for Arasan 4.1 bug */
9316         if (strcmp(r, "Black resigns") == 0) {
9317             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9318             return;
9319         }
9320         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9321         return;
9322     } else if (strncmp(message, "1/2", 3) == 0) {
9323         char *p, *q, *r = "";
9324         p = strchr(message, '{');
9325         if (p) {
9326             q = strchr(p, '}');
9327             if (q) {
9328                 *q = NULLCHAR;
9329                 r = p + 1;
9330             }
9331         }
9332
9333         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9334         return;
9335
9336     } else if (strncmp(message, "White resign", 12) == 0) {
9337         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9338         return;
9339     } else if (strncmp(message, "Black resign", 12) == 0) {
9340         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9341         return;
9342     } else if (strncmp(message, "White matches", 13) == 0 ||
9343                strncmp(message, "Black matches", 13) == 0   ) {
9344         /* [HGM] ignore GNUShogi noises */
9345         return;
9346     } else if (strncmp(message, "White", 5) == 0 &&
9347                message[5] != '(' &&
9348                StrStr(message, "Black") == NULL) {
9349         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9350         return;
9351     } else if (strncmp(message, "Black", 5) == 0 &&
9352                message[5] != '(') {
9353         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9354         return;
9355     } else if (strcmp(message, "resign") == 0 ||
9356                strcmp(message, "computer resigns") == 0) {
9357         switch (gameMode) {
9358           case MachinePlaysBlack:
9359           case IcsPlayingBlack:
9360             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9361             break;
9362           case MachinePlaysWhite:
9363           case IcsPlayingWhite:
9364             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9365             break;
9366           case TwoMachinesPlay:
9367             if (cps->twoMachinesColor[0] == 'w')
9368               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9369             else
9370               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9371             break;
9372           default:
9373             /* can't happen */
9374             break;
9375         }
9376         return;
9377     } else if (strncmp(message, "opponent mates", 14) == 0) {
9378         switch (gameMode) {
9379           case MachinePlaysBlack:
9380           case IcsPlayingBlack:
9381             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9382             break;
9383           case MachinePlaysWhite:
9384           case IcsPlayingWhite:
9385             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9386             break;
9387           case TwoMachinesPlay:
9388             if (cps->twoMachinesColor[0] == 'w')
9389               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9390             else
9391               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9392             break;
9393           default:
9394             /* can't happen */
9395             break;
9396         }
9397         return;
9398     } else if (strncmp(message, "computer mates", 14) == 0) {
9399         switch (gameMode) {
9400           case MachinePlaysBlack:
9401           case IcsPlayingBlack:
9402             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9403             break;
9404           case MachinePlaysWhite:
9405           case IcsPlayingWhite:
9406             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9407             break;
9408           case TwoMachinesPlay:
9409             if (cps->twoMachinesColor[0] == 'w')
9410               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9411             else
9412               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9413             break;
9414           default:
9415             /* can't happen */
9416             break;
9417         }
9418         return;
9419     } else if (strncmp(message, "checkmate", 9) == 0) {
9420         if (WhiteOnMove(forwardMostMove)) {
9421             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9422         } else {
9423             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9424         }
9425         return;
9426     } else if (strstr(message, "Draw") != NULL ||
9427                strstr(message, "game is a draw") != NULL) {
9428         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9429         return;
9430     } else if (strstr(message, "offer") != NULL &&
9431                strstr(message, "draw") != NULL) {
9432 #if ZIPPY
9433         if (appData.zippyPlay && first.initDone) {
9434             /* Relay offer to ICS */
9435             SendToICS(ics_prefix);
9436             SendToICS("draw\n");
9437         }
9438 #endif
9439         cps->offeredDraw = 2; /* valid until this engine moves twice */
9440         if (gameMode == TwoMachinesPlay) {
9441             if (cps->other->offeredDraw) {
9442                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9443             /* [HGM] in two-machine mode we delay relaying draw offer      */
9444             /* until after we also have move, to see if it is really claim */
9445             }
9446         } else if (gameMode == MachinePlaysWhite ||
9447                    gameMode == MachinePlaysBlack) {
9448           if (userOfferedDraw) {
9449             DisplayInformation(_("Machine accepts your draw offer"));
9450             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9451           } else {
9452             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9453           }
9454         }
9455     }
9456
9457
9458     /*
9459      * Look for thinking output
9460      */
9461     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9462           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9463                                 ) {
9464         int plylev, mvleft, mvtot, curscore, time;
9465         char mvname[MOVE_LEN];
9466         u64 nodes; // [DM]
9467         char plyext;
9468         int ignore = FALSE;
9469         int prefixHint = FALSE;
9470         mvname[0] = NULLCHAR;
9471
9472         switch (gameMode) {
9473           case MachinePlaysBlack:
9474           case IcsPlayingBlack:
9475             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9476             break;
9477           case MachinePlaysWhite:
9478           case IcsPlayingWhite:
9479             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9480             break;
9481           case AnalyzeMode:
9482           case AnalyzeFile:
9483             break;
9484           case IcsObserving: /* [DM] icsEngineAnalyze */
9485             if (!appData.icsEngineAnalyze) ignore = TRUE;
9486             break;
9487           case TwoMachinesPlay:
9488             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9489                 ignore = TRUE;
9490             }
9491             break;
9492           default:
9493             ignore = TRUE;
9494             break;
9495         }
9496
9497         if (!ignore) {
9498             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9499             buf1[0] = NULLCHAR;
9500             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9501                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9502
9503                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9504                     nodes += u64Const(0x100000000);
9505
9506                 if (plyext != ' ' && plyext != '\t') {
9507                     time *= 100;
9508                 }
9509
9510                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9511                 if( cps->scoreIsAbsolute &&
9512                     ( gameMode == MachinePlaysBlack ||
9513                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9514                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9515                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9516                      !WhiteOnMove(currentMove)
9517                     ) )
9518                 {
9519                     curscore = -curscore;
9520                 }
9521
9522                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9523
9524                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9525                         char buf[MSG_SIZ];
9526                         FILE *f;
9527                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9528                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9529                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9530                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9531                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9532                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9533                                 fclose(f);
9534                         }
9535                         else
9536                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9537                           DisplayError(_("failed writing PV"), 0);
9538                 }
9539
9540                 tempStats.depth = plylev;
9541                 tempStats.nodes = nodes;
9542                 tempStats.time = time;
9543                 tempStats.score = curscore;
9544                 tempStats.got_only_move = 0;
9545
9546                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9547                         int ticklen;
9548
9549                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9550                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9551                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9552                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9553                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9554                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9555                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9556                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9557                 }
9558
9559                 /* Buffer overflow protection */
9560                 if (pv[0] != NULLCHAR) {
9561                     if (strlen(pv) >= sizeof(tempStats.movelist)
9562                         && appData.debugMode) {
9563                         fprintf(debugFP,
9564                                 "PV is too long; using the first %u bytes.\n",
9565                                 (unsigned) sizeof(tempStats.movelist) - 1);
9566                     }
9567
9568                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9569                 } else {
9570                     sprintf(tempStats.movelist, " no PV\n");
9571                 }
9572
9573                 if (tempStats.seen_stat) {
9574                     tempStats.ok_to_send = 1;
9575                 }
9576
9577                 if (strchr(tempStats.movelist, '(') != NULL) {
9578                     tempStats.line_is_book = 1;
9579                     tempStats.nr_moves = 0;
9580                     tempStats.moves_left = 0;
9581                 } else {
9582                     tempStats.line_is_book = 0;
9583                 }
9584
9585                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9586                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9587
9588                 SendProgramStatsToFrontend( cps, &tempStats );
9589
9590                 /*
9591                     [AS] Protect the thinkOutput buffer from overflow... this
9592                     is only useful if buf1 hasn't overflowed first!
9593                 */
9594                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9595                          plylev,
9596                          (gameMode == TwoMachinesPlay ?
9597                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9598                          ((double) curscore) / 100.0,
9599                          prefixHint ? lastHint : "",
9600                          prefixHint ? " " : "" );
9601
9602                 if( buf1[0] != NULLCHAR ) {
9603                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9604
9605                     if( strlen(pv) > max_len ) {
9606                         if( appData.debugMode) {
9607                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9608                         }
9609                         pv[max_len+1] = '\0';
9610                     }
9611
9612                     strcat( thinkOutput, pv);
9613                 }
9614
9615                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9616                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9617                     DisplayMove(currentMove - 1);
9618                 }
9619                 return;
9620
9621             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9622                 /* crafty (9.25+) says "(only move) <move>"
9623                  * if there is only 1 legal move
9624                  */
9625                 sscanf(p, "(only move) %s", buf1);
9626                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9627                 sprintf(programStats.movelist, "%s (only move)", buf1);
9628                 programStats.depth = 1;
9629                 programStats.nr_moves = 1;
9630                 programStats.moves_left = 1;
9631                 programStats.nodes = 1;
9632                 programStats.time = 1;
9633                 programStats.got_only_move = 1;
9634
9635                 /* Not really, but we also use this member to
9636                    mean "line isn't going to change" (Crafty
9637                    isn't searching, so stats won't change) */
9638                 programStats.line_is_book = 1;
9639
9640                 SendProgramStatsToFrontend( cps, &programStats );
9641
9642                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9643                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9644                     DisplayMove(currentMove - 1);
9645                 }
9646                 return;
9647             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9648                               &time, &nodes, &plylev, &mvleft,
9649                               &mvtot, mvname) >= 5) {
9650                 /* The stat01: line is from Crafty (9.29+) in response
9651                    to the "." command */
9652                 programStats.seen_stat = 1;
9653                 cps->maybeThinking = TRUE;
9654
9655                 if (programStats.got_only_move || !appData.periodicUpdates)
9656                   return;
9657
9658                 programStats.depth = plylev;
9659                 programStats.time = time;
9660                 programStats.nodes = nodes;
9661                 programStats.moves_left = mvleft;
9662                 programStats.nr_moves = mvtot;
9663                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9664                 programStats.ok_to_send = 1;
9665                 programStats.movelist[0] = '\0';
9666
9667                 SendProgramStatsToFrontend( cps, &programStats );
9668
9669                 return;
9670
9671             } else if (strncmp(message,"++",2) == 0) {
9672                 /* Crafty 9.29+ outputs this */
9673                 programStats.got_fail = 2;
9674                 return;
9675
9676             } else if (strncmp(message,"--",2) == 0) {
9677                 /* Crafty 9.29+ outputs this */
9678                 programStats.got_fail = 1;
9679                 return;
9680
9681             } else if (thinkOutput[0] != NULLCHAR &&
9682                        strncmp(message, "    ", 4) == 0) {
9683                 unsigned message_len;
9684
9685                 p = message;
9686                 while (*p && *p == ' ') p++;
9687
9688                 message_len = strlen( p );
9689
9690                 /* [AS] Avoid buffer overflow */
9691                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9692                     strcat(thinkOutput, " ");
9693                     strcat(thinkOutput, p);
9694                 }
9695
9696                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9697                     strcat(programStats.movelist, " ");
9698                     strcat(programStats.movelist, p);
9699                 }
9700
9701                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9702                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9703                     DisplayMove(currentMove - 1);
9704                 }
9705                 return;
9706             }
9707         }
9708         else {
9709             buf1[0] = NULLCHAR;
9710
9711             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9712                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9713             {
9714                 ChessProgramStats cpstats;
9715
9716                 if (plyext != ' ' && plyext != '\t') {
9717                     time *= 100;
9718                 }
9719
9720                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9721                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9722                     curscore = -curscore;
9723                 }
9724
9725                 cpstats.depth = plylev;
9726                 cpstats.nodes = nodes;
9727                 cpstats.time = time;
9728                 cpstats.score = curscore;
9729                 cpstats.got_only_move = 0;
9730                 cpstats.movelist[0] = '\0';
9731
9732                 if (buf1[0] != NULLCHAR) {
9733                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9734                 }
9735
9736                 cpstats.ok_to_send = 0;
9737                 cpstats.line_is_book = 0;
9738                 cpstats.nr_moves = 0;
9739                 cpstats.moves_left = 0;
9740
9741                 SendProgramStatsToFrontend( cps, &cpstats );
9742             }
9743         }
9744     }
9745 }
9746
9747
9748 /* Parse a game score from the character string "game", and
9749    record it as the history of the current game.  The game
9750    score is NOT assumed to start from the standard position.
9751    The display is not updated in any way.
9752    */
9753 void
9754 ParseGameHistory (char *game)
9755 {
9756     ChessMove moveType;
9757     int fromX, fromY, toX, toY, boardIndex;
9758     char promoChar;
9759     char *p, *q;
9760     char buf[MSG_SIZ];
9761
9762     if (appData.debugMode)
9763       fprintf(debugFP, "Parsing game history: %s\n", game);
9764
9765     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9766     gameInfo.site = StrSave(appData.icsHost);
9767     gameInfo.date = PGNDate();
9768     gameInfo.round = StrSave("-");
9769
9770     /* Parse out names of players */
9771     while (*game == ' ') game++;
9772     p = buf;
9773     while (*game != ' ') *p++ = *game++;
9774     *p = NULLCHAR;
9775     gameInfo.white = StrSave(buf);
9776     while (*game == ' ') game++;
9777     p = buf;
9778     while (*game != ' ' && *game != '\n') *p++ = *game++;
9779     *p = NULLCHAR;
9780     gameInfo.black = StrSave(buf);
9781
9782     /* Parse moves */
9783     boardIndex = blackPlaysFirst ? 1 : 0;
9784     yynewstr(game);
9785     for (;;) {
9786         yyboardindex = boardIndex;
9787         moveType = (ChessMove) Myylex();
9788         switch (moveType) {
9789           case IllegalMove:             /* maybe suicide chess, etc. */
9790   if (appData.debugMode) {
9791     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9792     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9793     setbuf(debugFP, NULL);
9794   }
9795           case WhitePromotion:
9796           case BlackPromotion:
9797           case WhiteNonPromotion:
9798           case BlackNonPromotion:
9799           case NormalMove:
9800           case FirstLeg:
9801           case WhiteCapturesEnPassant:
9802           case BlackCapturesEnPassant:
9803           case WhiteKingSideCastle:
9804           case WhiteQueenSideCastle:
9805           case BlackKingSideCastle:
9806           case BlackQueenSideCastle:
9807           case WhiteKingSideCastleWild:
9808           case WhiteQueenSideCastleWild:
9809           case BlackKingSideCastleWild:
9810           case BlackQueenSideCastleWild:
9811           /* PUSH Fabien */
9812           case WhiteHSideCastleFR:
9813           case WhiteASideCastleFR:
9814           case BlackHSideCastleFR:
9815           case BlackASideCastleFR:
9816           /* POP Fabien */
9817             fromX = currentMoveString[0] - AAA;
9818             fromY = currentMoveString[1] - ONE;
9819             toX = currentMoveString[2] - AAA;
9820             toY = currentMoveString[3] - ONE;
9821             promoChar = currentMoveString[4];
9822             break;
9823           case WhiteDrop:
9824           case BlackDrop:
9825             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9826             fromX = moveType == WhiteDrop ?
9827               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9828             (int) CharToPiece(ToLower(currentMoveString[0]));
9829             fromY = DROP_RANK;
9830             toX = currentMoveString[2] - AAA;
9831             toY = currentMoveString[3] - ONE;
9832             promoChar = NULLCHAR;
9833             break;
9834           case AmbiguousMove:
9835             /* bug? */
9836             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9837   if (appData.debugMode) {
9838     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9839     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9840     setbuf(debugFP, NULL);
9841   }
9842             DisplayError(buf, 0);
9843             return;
9844           case ImpossibleMove:
9845             /* bug? */
9846             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9847   if (appData.debugMode) {
9848     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9849     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9850     setbuf(debugFP, NULL);
9851   }
9852             DisplayError(buf, 0);
9853             return;
9854           case EndOfFile:
9855             if (boardIndex < backwardMostMove) {
9856                 /* Oops, gap.  How did that happen? */
9857                 DisplayError(_("Gap in move list"), 0);
9858                 return;
9859             }
9860             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9861             if (boardIndex > forwardMostMove) {
9862                 forwardMostMove = boardIndex;
9863             }
9864             return;
9865           case ElapsedTime:
9866             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9867                 strcat(parseList[boardIndex-1], " ");
9868                 strcat(parseList[boardIndex-1], yy_text);
9869             }
9870             continue;
9871           case Comment:
9872           case PGNTag:
9873           case NAG:
9874           default:
9875             /* ignore */
9876             continue;
9877           case WhiteWins:
9878           case BlackWins:
9879           case GameIsDrawn:
9880           case GameUnfinished:
9881             if (gameMode == IcsExamining) {
9882                 if (boardIndex < backwardMostMove) {
9883                     /* Oops, gap.  How did that happen? */
9884                     return;
9885                 }
9886                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9887                 return;
9888             }
9889             gameInfo.result = moveType;
9890             p = strchr(yy_text, '{');
9891             if (p == NULL) p = strchr(yy_text, '(');
9892             if (p == NULL) {
9893                 p = yy_text;
9894                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9895             } else {
9896                 q = strchr(p, *p == '{' ? '}' : ')');
9897                 if (q != NULL) *q = NULLCHAR;
9898                 p++;
9899             }
9900             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9901             gameInfo.resultDetails = StrSave(p);
9902             continue;
9903         }
9904         if (boardIndex >= forwardMostMove &&
9905             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9906             backwardMostMove = blackPlaysFirst ? 1 : 0;
9907             return;
9908         }
9909         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9910                                  fromY, fromX, toY, toX, promoChar,
9911                                  parseList[boardIndex]);
9912         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9913         /* currentMoveString is set as a side-effect of yylex */
9914         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9915         strcat(moveList[boardIndex], "\n");
9916         boardIndex++;
9917         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9918         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9919           case MT_NONE:
9920           case MT_STALEMATE:
9921           default:
9922             break;
9923           case MT_CHECK:
9924             if(!IS_SHOGI(gameInfo.variant))
9925                 strcat(parseList[boardIndex - 1], "+");
9926             break;
9927           case MT_CHECKMATE:
9928           case MT_STAINMATE:
9929             strcat(parseList[boardIndex - 1], "#");
9930             break;
9931         }
9932     }
9933 }
9934
9935
9936 /* Apply a move to the given board  */
9937 void
9938 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9939 {
9940   ChessSquare captured = board[toY][toX], piece, pawn, king, killed; int p, rookX, oldEP, epRank, berolina = 0;
9941   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9942
9943     /* [HGM] compute & store e.p. status and castling rights for new position */
9944     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9945
9946       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9947       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
9948       board[EP_STATUS] = EP_NONE;
9949       board[EP_FILE] = board[EP_RANK] = 100;
9950
9951   if (fromY == DROP_RANK) {
9952         /* must be first */
9953         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9954             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9955             return;
9956         }
9957         piece = board[toY][toX] = (ChessSquare) fromX;
9958   } else {
9959 //      ChessSquare victim;
9960       int i;
9961
9962       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9963 //           victim = board[killY][killX],
9964            killed = board[killY][killX],
9965            board[killY][killX] = EmptySquare,
9966            board[EP_STATUS] = EP_CAPTURE;
9967
9968       if( board[toY][toX] != EmptySquare ) {
9969            board[EP_STATUS] = EP_CAPTURE;
9970            if( (fromX != toX || fromY != toY) && // not igui!
9971                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9972                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9973                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9974            }
9975       }
9976
9977       pawn = board[fromY][fromX];
9978       if( pawn == WhiteLance || pawn == BlackLance ) {
9979            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
9980                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
9981                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
9982            }
9983       }
9984       if( pawn == WhitePawn ) {
9985            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9986                board[EP_STATUS] = EP_PAWN_MOVE;
9987            if( toY-fromY>=2) {
9988                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
9989                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9990                         gameInfo.variant != VariantBerolina || toX < fromX)
9991                       board[EP_STATUS] = toX | berolina;
9992                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9993                         gameInfo.variant != VariantBerolina || toX > fromX)
9994                       board[EP_STATUS] = toX;
9995            }
9996       } else
9997       if( pawn == BlackPawn ) {
9998            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9999                board[EP_STATUS] = EP_PAWN_MOVE;
10000            if( toY-fromY<= -2) {
10001                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10002                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10003                         gameInfo.variant != VariantBerolina || toX < fromX)
10004                       board[EP_STATUS] = toX | berolina;
10005                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10006                         gameInfo.variant != VariantBerolina || toX > fromX)
10007                       board[EP_STATUS] = toX;
10008            }
10009        }
10010
10011        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10012        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10013        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10014        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10015
10016        for(i=0; i<nrCastlingRights; i++) {
10017            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10018               board[CASTLING][i] == toX   && castlingRank[i] == toY
10019              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10020        }
10021
10022        if(gameInfo.variant == VariantSChess) { // update virginity
10023            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10024            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10025            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10026            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10027        }
10028
10029      if (fromX == toX && fromY == toY) return;
10030
10031      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10032      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10033      if(gameInfo.variant == VariantKnightmate)
10034          king += (int) WhiteUnicorn - (int) WhiteKing;
10035
10036     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10037        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10038         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10039         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10040         board[EP_STATUS] = EP_NONE; // capture was fake!
10041     } else
10042     /* Code added by Tord: */
10043     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10044     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10045         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10046       board[EP_STATUS] = EP_NONE; // capture was fake!
10047       board[fromY][fromX] = EmptySquare;
10048       board[toY][toX] = EmptySquare;
10049       if((toX > fromX) != (piece == WhiteRook)) {
10050         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10051       } else {
10052         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10053       }
10054     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10055                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10056       board[EP_STATUS] = EP_NONE;
10057       board[fromY][fromX] = EmptySquare;
10058       board[toY][toX] = EmptySquare;
10059       if((toX > fromX) != (piece == BlackRook)) {
10060         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10061       } else {
10062         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10063       }
10064     /* End of code added by Tord */
10065
10066     } else if (board[fromY][fromX] == king
10067         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10068         && toY == fromY && toX > fromX+1) {
10069         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10070         board[fromY][toX-1] = board[fromY][rookX];
10071         board[fromY][rookX] = EmptySquare;
10072         board[fromY][fromX] = EmptySquare;
10073         board[toY][toX] = king;
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 > 0; 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] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10083                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10084                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10085                ) {
10086         /* white pawn promotion */
10087         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10088         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10089             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10090         board[fromY][fromX] = EmptySquare;
10091     } else if ((fromY >= BOARD_HEIGHT>>1)
10092                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10093                && (toX != fromX)
10094                && gameInfo.variant != VariantXiangqi
10095                && gameInfo.variant != VariantBerolina
10096                && (pawn == WhitePawn)
10097                && (board[toY][toX] == EmptySquare)) {
10098         board[fromY][fromX] = EmptySquare;
10099         board[toY][toX] = piece;
10100         if(toY == epRank - 128 + 1)
10101             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10102         else
10103             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10104     } else if ((fromY == BOARD_HEIGHT-4)
10105                && (toX == fromX)
10106                && gameInfo.variant == VariantBerolina
10107                && (board[fromY][fromX] == WhitePawn)
10108                && (board[toY][toX] == EmptySquare)) {
10109         board[fromY][fromX] = EmptySquare;
10110         board[toY][toX] = WhitePawn;
10111         if(oldEP & EP_BEROLIN_A) {
10112                 captured = board[fromY][fromX-1];
10113                 board[fromY][fromX-1] = EmptySquare;
10114         }else{  captured = board[fromY][fromX+1];
10115                 board[fromY][fromX+1] = EmptySquare;
10116         }
10117     } else if (board[fromY][fromX] == king
10118         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10119                && toY == fromY && toX > fromX+1) {
10120         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10121         board[fromY][toX-1] = board[fromY][rookX];
10122         board[fromY][rookX] = EmptySquare;
10123         board[fromY][fromX] = EmptySquare;
10124         board[toY][toX] = king;
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 > 0; 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 (fromY == 7 && fromX == 3
10134                && board[fromY][fromX] == BlackKing
10135                && toY == 7 && toX == 5) {
10136         board[fromY][fromX] = EmptySquare;
10137         board[toY][toX] = BlackKing;
10138         board[fromY][7] = EmptySquare;
10139         board[toY][4] = BlackRook;
10140     } else if (fromY == 7 && fromX == 3
10141                && board[fromY][fromX] == BlackKing
10142                && toY == 7 && toX == 1) {
10143         board[fromY][fromX] = EmptySquare;
10144         board[toY][toX] = BlackKing;
10145         board[fromY][0] = EmptySquare;
10146         board[toY][2] = BlackRook;
10147     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10148                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10149                && toY < promoRank && promoChar
10150                ) {
10151         /* black pawn promotion */
10152         board[toY][toX] = CharToPiece(ToLower(promoChar));
10153         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10154             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10155         board[fromY][fromX] = EmptySquare;
10156     } else if ((fromY < BOARD_HEIGHT>>1)
10157                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10158                && (toX != fromX)
10159                && gameInfo.variant != VariantXiangqi
10160                && gameInfo.variant != VariantBerolina
10161                && (pawn == BlackPawn)
10162                && (board[toY][toX] == EmptySquare)) {
10163         board[fromY][fromX] = EmptySquare;
10164         board[toY][toX] = piece;
10165         if(toY == epRank - 128 - 1)
10166             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10167         else
10168             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10169     } else if ((fromY == 3)
10170                && (toX == fromX)
10171                && gameInfo.variant == VariantBerolina
10172                && (board[fromY][fromX] == BlackPawn)
10173                && (board[toY][toX] == EmptySquare)) {
10174         board[fromY][fromX] = EmptySquare;
10175         board[toY][toX] = BlackPawn;
10176         if(oldEP & EP_BEROLIN_A) {
10177                 captured = board[fromY][fromX-1];
10178                 board[fromY][fromX-1] = EmptySquare;
10179         }else{  captured = board[fromY][fromX+1];
10180                 board[fromY][fromX+1] = EmptySquare;
10181         }
10182     } else {
10183         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10184         board[fromY][fromX] = EmptySquare;
10185         board[toY][toX] = piece;
10186     }
10187   }
10188
10189     if (gameInfo.holdingsWidth != 0) {
10190
10191       /* !!A lot more code needs to be written to support holdings  */
10192       /* [HGM] OK, so I have written it. Holdings are stored in the */
10193       /* penultimate board files, so they are automaticlly stored   */
10194       /* in the game history.                                       */
10195       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10196                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10197         /* Delete from holdings, by decreasing count */
10198         /* and erasing image if necessary            */
10199         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10200         if(p < (int) BlackPawn) { /* white drop */
10201              p -= (int)WhitePawn;
10202                  p = PieceToNumber((ChessSquare)p);
10203              if(p >= gameInfo.holdingsSize) p = 0;
10204              if(--board[p][BOARD_WIDTH-2] <= 0)
10205                   board[p][BOARD_WIDTH-1] = EmptySquare;
10206              if((int)board[p][BOARD_WIDTH-2] < 0)
10207                         board[p][BOARD_WIDTH-2] = 0;
10208         } else {                  /* black drop */
10209              p -= (int)BlackPawn;
10210                  p = PieceToNumber((ChessSquare)p);
10211              if(p >= gameInfo.holdingsSize) p = 0;
10212              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10213                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10214              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10215                         board[BOARD_HEIGHT-1-p][1] = 0;
10216         }
10217       }
10218       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10219           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10220         /* [HGM] holdings: Add to holdings, if holdings exist */
10221         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10222                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10223                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10224         }
10225         p = (int) captured;
10226         if (p >= (int) BlackPawn) {
10227           p -= (int)BlackPawn;
10228           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10229                   /* Restore shogi-promoted piece to its original  first */
10230                   captured = (ChessSquare) (DEMOTED captured);
10231                   p = DEMOTED p;
10232           }
10233           p = PieceToNumber((ChessSquare)p);
10234           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10235           board[p][BOARD_WIDTH-2]++;
10236           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10237         } else {
10238           p -= (int)WhitePawn;
10239           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10240                   captured = (ChessSquare) (DEMOTED captured);
10241                   p = DEMOTED p;
10242           }
10243           p = PieceToNumber((ChessSquare)p);
10244           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10245           board[BOARD_HEIGHT-1-p][1]++;
10246           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10247         }
10248       }
10249     } else if (gameInfo.variant == VariantAtomic) {
10250       if (captured != EmptySquare) {
10251         int y, x;
10252         for (y = toY-1; y <= toY+1; y++) {
10253           for (x = toX-1; x <= toX+1; x++) {
10254             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10255                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10256               board[y][x] = EmptySquare;
10257             }
10258           }
10259         }
10260         board[toY][toX] = EmptySquare;
10261       }
10262     }
10263
10264     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10265         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10266     } else
10267     if(promoChar == '+') {
10268         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10269         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10270         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10271           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10272     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10273         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10274         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10275            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10276         board[toY][toX] = newPiece;
10277     }
10278     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10279                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10280         // [HGM] superchess: take promotion piece out of holdings
10281         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10282         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10283             if(!--board[k][BOARD_WIDTH-2])
10284                 board[k][BOARD_WIDTH-1] = EmptySquare;
10285         } else {
10286             if(!--board[BOARD_HEIGHT-1-k][1])
10287                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10288         }
10289     }
10290 }
10291
10292 /* Updates forwardMostMove */
10293 void
10294 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10295 {
10296     int x = toX, y = toY;
10297     char *s = parseList[forwardMostMove];
10298     ChessSquare p = boards[forwardMostMove][toY][toX];
10299 //    forwardMostMove++; // [HGM] bare: moved downstream
10300
10301     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10302     (void) CoordsToAlgebraic(boards[forwardMostMove],
10303                              PosFlags(forwardMostMove),
10304                              fromY, fromX, y, x, promoChar,
10305                              s);
10306     if(killX >= 0 && killY >= 0)
10307         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10308
10309     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10310         int timeLeft; static int lastLoadFlag=0; int king, piece;
10311         piece = boards[forwardMostMove][fromY][fromX];
10312         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10313         if(gameInfo.variant == VariantKnightmate)
10314             king += (int) WhiteUnicorn - (int) WhiteKing;
10315         if(forwardMostMove == 0) {
10316             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10317                 fprintf(serverMoves, "%s;", UserName());
10318             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10319                 fprintf(serverMoves, "%s;", second.tidy);
10320             fprintf(serverMoves, "%s;", first.tidy);
10321             if(gameMode == MachinePlaysWhite)
10322                 fprintf(serverMoves, "%s;", UserName());
10323             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10324                 fprintf(serverMoves, "%s;", second.tidy);
10325         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10326         lastLoadFlag = loadFlag;
10327         // print base move
10328         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10329         // print castling suffix
10330         if( toY == fromY && piece == king ) {
10331             if(toX-fromX > 1)
10332                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10333             if(fromX-toX >1)
10334                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10335         }
10336         // e.p. suffix
10337         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10338              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10339              boards[forwardMostMove][toY][toX] == EmptySquare
10340              && fromX != toX && fromY != toY)
10341                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10342         // promotion suffix
10343         if(promoChar != NULLCHAR) {
10344             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10345                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10346                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10347             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10348         }
10349         if(!loadFlag) {
10350                 char buf[MOVE_LEN*2], *p; int len;
10351             fprintf(serverMoves, "/%d/%d",
10352                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10353             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10354             else                      timeLeft = blackTimeRemaining/1000;
10355             fprintf(serverMoves, "/%d", timeLeft);
10356                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10357                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10358                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10359                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10360             fprintf(serverMoves, "/%s", buf);
10361         }
10362         fflush(serverMoves);
10363     }
10364
10365     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10366         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10367       return;
10368     }
10369     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10370     if (commentList[forwardMostMove+1] != NULL) {
10371         free(commentList[forwardMostMove+1]);
10372         commentList[forwardMostMove+1] = NULL;
10373     }
10374     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10375     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10376     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10377     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10378     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10379     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10380     adjustedClock = FALSE;
10381     gameInfo.result = GameUnfinished;
10382     if (gameInfo.resultDetails != NULL) {
10383         free(gameInfo.resultDetails);
10384         gameInfo.resultDetails = NULL;
10385     }
10386     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10387                               moveList[forwardMostMove - 1]);
10388     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10389       case MT_NONE:
10390       case MT_STALEMATE:
10391       default:
10392         break;
10393       case MT_CHECK:
10394         if(!IS_SHOGI(gameInfo.variant))
10395             strcat(parseList[forwardMostMove - 1], "+");
10396         break;
10397       case MT_CHECKMATE:
10398       case MT_STAINMATE:
10399         strcat(parseList[forwardMostMove - 1], "#");
10400         break;
10401     }
10402 }
10403
10404 /* Updates currentMove if not pausing */
10405 void
10406 ShowMove (int fromX, int fromY, int toX, int toY)
10407 {
10408     int instant = (gameMode == PlayFromGameFile) ?
10409         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10410     if(appData.noGUI) return;
10411     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10412         if (!instant) {
10413             if (forwardMostMove == currentMove + 1) {
10414                 AnimateMove(boards[forwardMostMove - 1],
10415                             fromX, fromY, toX, toY);
10416             }
10417         }
10418         currentMove = forwardMostMove;
10419     }
10420
10421     killX = killY = -1; // [HGM] lion: used up
10422
10423     if (instant) return;
10424
10425     DisplayMove(currentMove - 1);
10426     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10427             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10428                 SetHighlights(fromX, fromY, toX, toY);
10429             }
10430     }
10431     DrawPosition(FALSE, boards[currentMove]);
10432     DisplayBothClocks();
10433     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10434 }
10435
10436 void
10437 SendEgtPath (ChessProgramState *cps)
10438 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10439         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10440
10441         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10442
10443         while(*p) {
10444             char c, *q = name+1, *r, *s;
10445
10446             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10447             while(*p && *p != ',') *q++ = *p++;
10448             *q++ = ':'; *q = 0;
10449             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10450                 strcmp(name, ",nalimov:") == 0 ) {
10451                 // take nalimov path from the menu-changeable option first, if it is defined
10452               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10453                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10454             } else
10455             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10456                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10457                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10458                 s = r = StrStr(s, ":") + 1; // beginning of path info
10459                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10460                 c = *r; *r = 0;             // temporarily null-terminate path info
10461                     *--q = 0;               // strip of trailig ':' from name
10462                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10463                 *r = c;
10464                 SendToProgram(buf,cps);     // send egtbpath command for this format
10465             }
10466             if(*p == ',') p++; // read away comma to position for next format name
10467         }
10468 }
10469
10470 static int
10471 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10472 {
10473       int width = 8, height = 8, holdings = 0;             // most common sizes
10474       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10475       // correct the deviations default for each variant
10476       if( v == VariantXiangqi ) width = 9,  height = 10;
10477       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10478       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10479       if( v == VariantCapablanca || v == VariantCapaRandom ||
10480           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10481                                 width = 10;
10482       if( v == VariantCourier ) width = 12;
10483       if( v == VariantSuper )                            holdings = 8;
10484       if( v == VariantGreat )   width = 10,              holdings = 8;
10485       if( v == VariantSChess )                           holdings = 7;
10486       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10487       if( v == VariantChuChess) width = 10, height = 10;
10488       if( v == VariantChu )     width = 12, height = 12;
10489       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10490              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10491              holdingsSize >= 0 && holdingsSize != holdings;
10492 }
10493
10494 char variantError[MSG_SIZ];
10495
10496 char *
10497 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10498 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10499       char *p, *variant = VariantName(v);
10500       static char b[MSG_SIZ];
10501       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10502            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10503                                                holdingsSize, variant); // cook up sized variant name
10504            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10505            if(StrStr(list, b) == NULL) {
10506                // specific sized variant not known, check if general sizing allowed
10507                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10508                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10509                             boardWidth, boardHeight, holdingsSize, engine);
10510                    return NULL;
10511                }
10512                /* [HGM] here we really should compare with the maximum supported board size */
10513            }
10514       } else snprintf(b, MSG_SIZ,"%s", variant);
10515       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10516       p = StrStr(list, b);
10517       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10518       if(p == NULL) {
10519           // occurs not at all in list, or only as sub-string
10520           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10521           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10522               int l = strlen(variantError);
10523               char *q;
10524               while(p != list && p[-1] != ',') p--;
10525               q = strchr(p, ',');
10526               if(q) *q = NULLCHAR;
10527               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10528               if(q) *q= ',';
10529           }
10530           return NULL;
10531       }
10532       return b;
10533 }
10534
10535 void
10536 InitChessProgram (ChessProgramState *cps, int setup)
10537 /* setup needed to setup FRC opening position */
10538 {
10539     char buf[MSG_SIZ], *b;
10540     if (appData.noChessProgram) return;
10541     hintRequested = FALSE;
10542     bookRequested = FALSE;
10543
10544     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10545     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10546     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10547     if(cps->memSize) { /* [HGM] memory */
10548       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10549         SendToProgram(buf, cps);
10550     }
10551     SendEgtPath(cps); /* [HGM] EGT */
10552     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10553       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10554         SendToProgram(buf, cps);
10555     }
10556
10557     setboardSpoiledMachineBlack = FALSE;
10558     SendToProgram(cps->initString, cps);
10559     if (gameInfo.variant != VariantNormal &&
10560         gameInfo.variant != VariantLoadable
10561         /* [HGM] also send variant if board size non-standard */
10562         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10563
10564       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10565                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10566       if (b == NULL) {
10567         VariantClass v;
10568         char c, *q = cps->variants, *p = strchr(q, ',');
10569         if(p) *p = NULLCHAR;
10570         v = StringToVariant(q);
10571         DisplayError(variantError, 0);
10572         if(v != VariantUnknown && cps == &first) {
10573             int w, h, s;
10574             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10575                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10576             ASSIGN(appData.variant, q);
10577             Reset(TRUE, FALSE);
10578         }
10579         if(p) *p = ',';
10580         return;
10581       }
10582
10583       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10584       SendToProgram(buf, cps);
10585     }
10586     currentlyInitializedVariant = gameInfo.variant;
10587
10588     /* [HGM] send opening position in FRC to first engine */
10589     if(setup) {
10590           SendToProgram("force\n", cps);
10591           SendBoard(cps, 0);
10592           /* engine is now in force mode! Set flag to wake it up after first move. */
10593           setboardSpoiledMachineBlack = 1;
10594     }
10595
10596     if (cps->sendICS) {
10597       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10598       SendToProgram(buf, cps);
10599     }
10600     cps->maybeThinking = FALSE;
10601     cps->offeredDraw = 0;
10602     if (!appData.icsActive) {
10603         SendTimeControl(cps, movesPerSession, timeControl,
10604                         timeIncrement, appData.searchDepth,
10605                         searchTime);
10606     }
10607     if (appData.showThinking
10608         // [HGM] thinking: four options require thinking output to be sent
10609         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10610                                 ) {
10611         SendToProgram("post\n", cps);
10612     }
10613     SendToProgram("hard\n", cps);
10614     if (!appData.ponderNextMove) {
10615         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10616            it without being sure what state we are in first.  "hard"
10617            is not a toggle, so that one is OK.
10618          */
10619         SendToProgram("easy\n", cps);
10620     }
10621     if (cps->usePing) {
10622       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10623       SendToProgram(buf, cps);
10624     }
10625     cps->initDone = TRUE;
10626     ClearEngineOutputPane(cps == &second);
10627 }
10628
10629
10630 void
10631 ResendOptions (ChessProgramState *cps)
10632 { // send the stored value of the options
10633   int i;
10634   char buf[MSG_SIZ];
10635   Option *opt = cps->option;
10636   for(i=0; i<cps->nrOptions; i++, opt++) {
10637       switch(opt->type) {
10638         case Spin:
10639         case Slider:
10640         case CheckBox:
10641             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10642           break;
10643         case ComboBox:
10644           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10645           break;
10646         default:
10647             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10648           break;
10649         case Button:
10650         case SaveButton:
10651           continue;
10652       }
10653       SendToProgram(buf, cps);
10654   }
10655 }
10656
10657 void
10658 StartChessProgram (ChessProgramState *cps)
10659 {
10660     char buf[MSG_SIZ];
10661     int err;
10662
10663     if (appData.noChessProgram) return;
10664     cps->initDone = FALSE;
10665
10666     if (strcmp(cps->host, "localhost") == 0) {
10667         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10668     } else if (*appData.remoteShell == NULLCHAR) {
10669         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10670     } else {
10671         if (*appData.remoteUser == NULLCHAR) {
10672           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10673                     cps->program);
10674         } else {
10675           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10676                     cps->host, appData.remoteUser, cps->program);
10677         }
10678         err = StartChildProcess(buf, "", &cps->pr);
10679     }
10680
10681     if (err != 0) {
10682       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10683         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10684         if(cps != &first) return;
10685         appData.noChessProgram = TRUE;
10686         ThawUI();
10687         SetNCPMode();
10688 //      DisplayFatalError(buf, err, 1);
10689 //      cps->pr = NoProc;
10690 //      cps->isr = NULL;
10691         return;
10692     }
10693
10694     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10695     if (cps->protocolVersion > 1) {
10696       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10697       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10698         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10699         cps->comboCnt = 0;  //                and values of combo boxes
10700       }
10701       SendToProgram(buf, cps);
10702       if(cps->reload) ResendOptions(cps);
10703     } else {
10704       SendToProgram("xboard\n", cps);
10705     }
10706 }
10707
10708 void
10709 TwoMachinesEventIfReady P((void))
10710 {
10711   static int curMess = 0;
10712   if (first.lastPing != first.lastPong) {
10713     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10714     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10715     return;
10716   }
10717   if (second.lastPing != second.lastPong) {
10718     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10719     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10720     return;
10721   }
10722   DisplayMessage("", ""); curMess = 0;
10723   TwoMachinesEvent();
10724 }
10725
10726 char *
10727 MakeName (char *template)
10728 {
10729     time_t clock;
10730     struct tm *tm;
10731     static char buf[MSG_SIZ];
10732     char *p = buf;
10733     int i;
10734
10735     clock = time((time_t *)NULL);
10736     tm = localtime(&clock);
10737
10738     while(*p++ = *template++) if(p[-1] == '%') {
10739         switch(*template++) {
10740           case 0:   *p = 0; return buf;
10741           case 'Y': i = tm->tm_year+1900; break;
10742           case 'y': i = tm->tm_year-100; break;
10743           case 'M': i = tm->tm_mon+1; break;
10744           case 'd': i = tm->tm_mday; break;
10745           case 'h': i = tm->tm_hour; break;
10746           case 'm': i = tm->tm_min; break;
10747           case 's': i = tm->tm_sec; break;
10748           default:  i = 0;
10749         }
10750         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10751     }
10752     return buf;
10753 }
10754
10755 int
10756 CountPlayers (char *p)
10757 {
10758     int n = 0;
10759     while(p = strchr(p, '\n')) p++, n++; // count participants
10760     return n;
10761 }
10762
10763 FILE *
10764 WriteTourneyFile (char *results, FILE *f)
10765 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10766     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10767     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10768         // create a file with tournament description
10769         fprintf(f, "-participants {%s}\n", appData.participants);
10770         fprintf(f, "-seedBase %d\n", appData.seedBase);
10771         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10772         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10773         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10774         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10775         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10776         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10777         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10778         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10779         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10780         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10781         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10782         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10783         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10784         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10785         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10786         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10787         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10788         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10789         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10790         fprintf(f, "-smpCores %d\n", appData.smpCores);
10791         if(searchTime > 0)
10792                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10793         else {
10794                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10795                 fprintf(f, "-tc %s\n", appData.timeControl);
10796                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10797         }
10798         fprintf(f, "-results \"%s\"\n", results);
10799     }
10800     return f;
10801 }
10802
10803 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10804
10805 void
10806 Substitute (char *participants, int expunge)
10807 {
10808     int i, changed, changes=0, nPlayers=0;
10809     char *p, *q, *r, buf[MSG_SIZ];
10810     if(participants == NULL) return;
10811     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10812     r = p = participants; q = appData.participants;
10813     while(*p && *p == *q) {
10814         if(*p == '\n') r = p+1, nPlayers++;
10815         p++; q++;
10816     }
10817     if(*p) { // difference
10818         while(*p && *p++ != '\n');
10819         while(*q && *q++ != '\n');
10820       changed = nPlayers;
10821         changes = 1 + (strcmp(p, q) != 0);
10822     }
10823     if(changes == 1) { // a single engine mnemonic was changed
10824         q = r; while(*q) nPlayers += (*q++ == '\n');
10825         p = buf; while(*r && (*p = *r++) != '\n') p++;
10826         *p = NULLCHAR;
10827         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10828         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10829         if(mnemonic[i]) { // The substitute is valid
10830             FILE *f;
10831             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10832                 flock(fileno(f), LOCK_EX);
10833                 ParseArgsFromFile(f);
10834                 fseek(f, 0, SEEK_SET);
10835                 FREE(appData.participants); appData.participants = participants;
10836                 if(expunge) { // erase results of replaced engine
10837                     int len = strlen(appData.results), w, b, dummy;
10838                     for(i=0; i<len; i++) {
10839                         Pairing(i, nPlayers, &w, &b, &dummy);
10840                         if((w == changed || b == changed) && appData.results[i] == '*') {
10841                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10842                             fclose(f);
10843                             return;
10844                         }
10845                     }
10846                     for(i=0; i<len; i++) {
10847                         Pairing(i, nPlayers, &w, &b, &dummy);
10848                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10849                     }
10850                 }
10851                 WriteTourneyFile(appData.results, f);
10852                 fclose(f); // release lock
10853                 return;
10854             }
10855         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10856     }
10857     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10858     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10859     free(participants);
10860     return;
10861 }
10862
10863 int
10864 CheckPlayers (char *participants)
10865 {
10866         int i;
10867         char buf[MSG_SIZ], *p;
10868         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10869         while(p = strchr(participants, '\n')) {
10870             *p = NULLCHAR;
10871             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10872             if(!mnemonic[i]) {
10873                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10874                 *p = '\n';
10875                 DisplayError(buf, 0);
10876                 return 1;
10877             }
10878             *p = '\n';
10879             participants = p + 1;
10880         }
10881         return 0;
10882 }
10883
10884 int
10885 CreateTourney (char *name)
10886 {
10887         FILE *f;
10888         if(matchMode && strcmp(name, appData.tourneyFile)) {
10889              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10890         }
10891         if(name[0] == NULLCHAR) {
10892             if(appData.participants[0])
10893                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10894             return 0;
10895         }
10896         f = fopen(name, "r");
10897         if(f) { // file exists
10898             ASSIGN(appData.tourneyFile, name);
10899             ParseArgsFromFile(f); // parse it
10900         } else {
10901             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10902             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10903                 DisplayError(_("Not enough participants"), 0);
10904                 return 0;
10905             }
10906             if(CheckPlayers(appData.participants)) return 0;
10907             ASSIGN(appData.tourneyFile, name);
10908             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10909             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10910         }
10911         fclose(f);
10912         appData.noChessProgram = FALSE;
10913         appData.clockMode = TRUE;
10914         SetGNUMode();
10915         return 1;
10916 }
10917
10918 int
10919 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10920 {
10921     char buf[MSG_SIZ], *p, *q;
10922     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10923     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10924     skip = !all && group[0]; // if group requested, we start in skip mode
10925     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10926         p = names; q = buf; header = 0;
10927         while(*p && *p != '\n') *q++ = *p++;
10928         *q = 0;
10929         if(*p == '\n') p++;
10930         if(buf[0] == '#') {
10931             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10932             depth++; // we must be entering a new group
10933             if(all) continue; // suppress printing group headers when complete list requested
10934             header = 1;
10935             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10936         }
10937         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10938         if(engineList[i]) free(engineList[i]);
10939         engineList[i] = strdup(buf);
10940         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10941         if(engineMnemonic[i]) free(engineMnemonic[i]);
10942         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10943             strcat(buf, " (");
10944             sscanf(q + 8, "%s", buf + strlen(buf));
10945             strcat(buf, ")");
10946         }
10947         engineMnemonic[i] = strdup(buf);
10948         i++;
10949     }
10950     engineList[i] = engineMnemonic[i] = NULL;
10951     return i;
10952 }
10953
10954 // following implemented as macro to avoid type limitations
10955 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10956
10957 void
10958 SwapEngines (int n)
10959 {   // swap settings for first engine and other engine (so far only some selected options)
10960     int h;
10961     char *p;
10962     if(n == 0) return;
10963     SWAP(directory, p)
10964     SWAP(chessProgram, p)
10965     SWAP(isUCI, h)
10966     SWAP(hasOwnBookUCI, h)
10967     SWAP(protocolVersion, h)
10968     SWAP(reuse, h)
10969     SWAP(scoreIsAbsolute, h)
10970     SWAP(timeOdds, h)
10971     SWAP(logo, p)
10972     SWAP(pgnName, p)
10973     SWAP(pvSAN, h)
10974     SWAP(engOptions, p)
10975     SWAP(engInitString, p)
10976     SWAP(computerString, p)
10977     SWAP(features, p)
10978     SWAP(fenOverride, p)
10979     SWAP(NPS, h)
10980     SWAP(accumulateTC, h)
10981     SWAP(drawDepth, h)
10982     SWAP(host, p)
10983     SWAP(pseudo, h)
10984 }
10985
10986 int
10987 GetEngineLine (char *s, int n)
10988 {
10989     int i;
10990     char buf[MSG_SIZ];
10991     extern char *icsNames;
10992     if(!s || !*s) return 0;
10993     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10994     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10995     if(!mnemonic[i]) return 0;
10996     if(n == 11) return 1; // just testing if there was a match
10997     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10998     if(n == 1) SwapEngines(n);
10999     ParseArgsFromString(buf);
11000     if(n == 1) SwapEngines(n);
11001     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11002         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11003         ParseArgsFromString(buf);
11004     }
11005     return 1;
11006 }
11007
11008 int
11009 SetPlayer (int player, char *p)
11010 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11011     int i;
11012     char buf[MSG_SIZ], *engineName;
11013     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11014     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11015     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11016     if(mnemonic[i]) {
11017         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11018         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11019         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11020         ParseArgsFromString(buf);
11021     } else { // no engine with this nickname is installed!
11022         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11023         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11024         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11025         ModeHighlight();
11026         DisplayError(buf, 0);
11027         return 0;
11028     }
11029     free(engineName);
11030     return i;
11031 }
11032
11033 char *recentEngines;
11034
11035 void
11036 RecentEngineEvent (int nr)
11037 {
11038     int n;
11039 //    SwapEngines(1); // bump first to second
11040 //    ReplaceEngine(&second, 1); // and load it there
11041     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11042     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11043     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11044         ReplaceEngine(&first, 0);
11045         FloatToFront(&appData.recentEngineList, command[n]);
11046     }
11047 }
11048
11049 int
11050 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11051 {   // determine players from game number
11052     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11053
11054     if(appData.tourneyType == 0) {
11055         roundsPerCycle = (nPlayers - 1) | 1;
11056         pairingsPerRound = nPlayers / 2;
11057     } else if(appData.tourneyType > 0) {
11058         roundsPerCycle = nPlayers - appData.tourneyType;
11059         pairingsPerRound = appData.tourneyType;
11060     }
11061     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11062     gamesPerCycle = gamesPerRound * roundsPerCycle;
11063     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11064     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11065     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11066     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11067     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11068     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11069
11070     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11071     if(appData.roundSync) *syncInterval = gamesPerRound;
11072
11073     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11074
11075     if(appData.tourneyType == 0) {
11076         if(curPairing == (nPlayers-1)/2 ) {
11077             *whitePlayer = curRound;
11078             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11079         } else {
11080             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11081             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11082             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11083             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11084         }
11085     } else if(appData.tourneyType > 1) {
11086         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11087         *whitePlayer = curRound + appData.tourneyType;
11088     } else if(appData.tourneyType > 0) {
11089         *whitePlayer = curPairing;
11090         *blackPlayer = curRound + appData.tourneyType;
11091     }
11092
11093     // take care of white/black alternation per round.
11094     // For cycles and games this is already taken care of by default, derived from matchGame!
11095     return curRound & 1;
11096 }
11097
11098 int
11099 NextTourneyGame (int nr, int *swapColors)
11100 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11101     char *p, *q;
11102     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11103     FILE *tf;
11104     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11105     tf = fopen(appData.tourneyFile, "r");
11106     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11107     ParseArgsFromFile(tf); fclose(tf);
11108     InitTimeControls(); // TC might be altered from tourney file
11109
11110     nPlayers = CountPlayers(appData.participants); // count participants
11111     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11112     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11113
11114     if(syncInterval) {
11115         p = q = appData.results;
11116         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11117         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11118             DisplayMessage(_("Waiting for other game(s)"),"");
11119             waitingForGame = TRUE;
11120             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11121             return 0;
11122         }
11123         waitingForGame = FALSE;
11124     }
11125
11126     if(appData.tourneyType < 0) {
11127         if(nr>=0 && !pairingReceived) {
11128             char buf[1<<16];
11129             if(pairing.pr == NoProc) {
11130                 if(!appData.pairingEngine[0]) {
11131                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11132                     return 0;
11133                 }
11134                 StartChessProgram(&pairing); // starts the pairing engine
11135             }
11136             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11137             SendToProgram(buf, &pairing);
11138             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11139             SendToProgram(buf, &pairing);
11140             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11141         }
11142         pairingReceived = 0;                              // ... so we continue here
11143         *swapColors = 0;
11144         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11145         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11146         matchGame = 1; roundNr = nr / syncInterval + 1;
11147     }
11148
11149     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11150
11151     // redefine engines, engine dir, etc.
11152     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11153     if(first.pr == NoProc) {
11154       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11155       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11156     }
11157     if(second.pr == NoProc) {
11158       SwapEngines(1);
11159       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11160       SwapEngines(1);         // and make that valid for second engine by swapping
11161       InitEngine(&second, 1);
11162     }
11163     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11164     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11165     return OK;
11166 }
11167
11168 void
11169 NextMatchGame ()
11170 {   // performs game initialization that does not invoke engines, and then tries to start the game
11171     int res, firstWhite, swapColors = 0;
11172     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11173     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
11174         char buf[MSG_SIZ];
11175         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11176         if(strcmp(buf, currentDebugFile)) { // name has changed
11177             FILE *f = fopen(buf, "w");
11178             if(f) { // if opening the new file failed, just keep using the old one
11179                 ASSIGN(currentDebugFile, buf);
11180                 fclose(debugFP);
11181                 debugFP = f;
11182             }
11183             if(appData.serverFileName) {
11184                 if(serverFP) fclose(serverFP);
11185                 serverFP = fopen(appData.serverFileName, "w");
11186                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11187                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11188             }
11189         }
11190     }
11191     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11192     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11193     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11194     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11195     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11196     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11197     Reset(FALSE, first.pr != NoProc);
11198     res = LoadGameOrPosition(matchGame); // setup game
11199     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11200     if(!res) return; // abort when bad game/pos file
11201     TwoMachinesEvent();
11202 }
11203
11204 void
11205 UserAdjudicationEvent (int result)
11206 {
11207     ChessMove gameResult = GameIsDrawn;
11208
11209     if( result > 0 ) {
11210         gameResult = WhiteWins;
11211     }
11212     else if( result < 0 ) {
11213         gameResult = BlackWins;
11214     }
11215
11216     if( gameMode == TwoMachinesPlay ) {
11217         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11218     }
11219 }
11220
11221
11222 // [HGM] save: calculate checksum of game to make games easily identifiable
11223 int
11224 StringCheckSum (char *s)
11225 {
11226         int i = 0;
11227         if(s==NULL) return 0;
11228         while(*s) i = i*259 + *s++;
11229         return i;
11230 }
11231
11232 int
11233 GameCheckSum ()
11234 {
11235         int i, sum=0;
11236         for(i=backwardMostMove; i<forwardMostMove; i++) {
11237                 sum += pvInfoList[i].depth;
11238                 sum += StringCheckSum(parseList[i]);
11239                 sum += StringCheckSum(commentList[i]);
11240                 sum *= 261;
11241         }
11242         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11243         return sum + StringCheckSum(commentList[i]);
11244 } // end of save patch
11245
11246 void
11247 GameEnds (ChessMove result, char *resultDetails, int whosays)
11248 {
11249     GameMode nextGameMode;
11250     int isIcsGame;
11251     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11252
11253     if(endingGame) return; /* [HGM] crash: forbid recursion */
11254     endingGame = 1;
11255     if(twoBoards) { // [HGM] dual: switch back to one board
11256         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11257         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11258     }
11259     if (appData.debugMode) {
11260       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11261               result, resultDetails ? resultDetails : "(null)", whosays);
11262     }
11263
11264     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11265
11266     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11267
11268     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11269         /* If we are playing on ICS, the server decides when the
11270            game is over, but the engine can offer to draw, claim
11271            a draw, or resign.
11272          */
11273 #if ZIPPY
11274         if (appData.zippyPlay && first.initDone) {
11275             if (result == GameIsDrawn) {
11276                 /* In case draw still needs to be claimed */
11277                 SendToICS(ics_prefix);
11278                 SendToICS("draw\n");
11279             } else if (StrCaseStr(resultDetails, "resign")) {
11280                 SendToICS(ics_prefix);
11281                 SendToICS("resign\n");
11282             }
11283         }
11284 #endif
11285         endingGame = 0; /* [HGM] crash */
11286         return;
11287     }
11288
11289     /* If we're loading the game from a file, stop */
11290     if (whosays == GE_FILE) {
11291       (void) StopLoadGameTimer();
11292       gameFileFP = NULL;
11293     }
11294
11295     /* Cancel draw offers */
11296     first.offeredDraw = second.offeredDraw = 0;
11297
11298     /* If this is an ICS game, only ICS can really say it's done;
11299        if not, anyone can. */
11300     isIcsGame = (gameMode == IcsPlayingWhite ||
11301                  gameMode == IcsPlayingBlack ||
11302                  gameMode == IcsObserving    ||
11303                  gameMode == IcsExamining);
11304
11305     if (!isIcsGame || whosays == GE_ICS) {
11306         /* OK -- not an ICS game, or ICS said it was done */
11307         StopClocks();
11308         if (!isIcsGame && !appData.noChessProgram)
11309           SetUserThinkingEnables();
11310
11311         /* [HGM] if a machine claims the game end we verify this claim */
11312         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11313             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11314                 char claimer;
11315                 ChessMove trueResult = (ChessMove) -1;
11316
11317                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11318                                             first.twoMachinesColor[0] :
11319                                             second.twoMachinesColor[0] ;
11320
11321                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11322                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11323                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11324                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11325                 } else
11326                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11327                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11328                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11329                 } else
11330                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11331                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11332                 }
11333
11334                 // now verify win claims, but not in drop games, as we don't understand those yet
11335                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11336                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11337                     (result == WhiteWins && claimer == 'w' ||
11338                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11339                       if (appData.debugMode) {
11340                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11341                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11342                       }
11343                       if(result != trueResult) {
11344                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11345                               result = claimer == 'w' ? BlackWins : WhiteWins;
11346                               resultDetails = buf;
11347                       }
11348                 } else
11349                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11350                     && (forwardMostMove <= backwardMostMove ||
11351                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11352                         (claimer=='b')==(forwardMostMove&1))
11353                                                                                   ) {
11354                       /* [HGM] verify: draws that were not flagged are false claims */
11355                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11356                       result = claimer == 'w' ? BlackWins : WhiteWins;
11357                       resultDetails = buf;
11358                 }
11359                 /* (Claiming a loss is accepted no questions asked!) */
11360             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11361                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11362                 result = GameUnfinished;
11363                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11364             }
11365             /* [HGM] bare: don't allow bare King to win */
11366             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11367                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11368                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11369                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11370                && result != GameIsDrawn)
11371             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11372                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11373                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11374                         if(p >= 0 && p <= (int)WhiteKing) k++;
11375                 }
11376                 if (appData.debugMode) {
11377                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11378                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11379                 }
11380                 if(k <= 1) {
11381                         result = GameIsDrawn;
11382                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11383                         resultDetails = buf;
11384                 }
11385             }
11386         }
11387
11388
11389         if(serverMoves != NULL && !loadFlag) { char c = '=';
11390             if(result==WhiteWins) c = '+';
11391             if(result==BlackWins) c = '-';
11392             if(resultDetails != NULL)
11393                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11394         }
11395         if (resultDetails != NULL) {
11396             gameInfo.result = result;
11397             gameInfo.resultDetails = StrSave(resultDetails);
11398
11399             /* display last move only if game was not loaded from file */
11400             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11401                 DisplayMove(currentMove - 1);
11402
11403             if (forwardMostMove != 0) {
11404                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11405                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11406                                                                 ) {
11407                     if (*appData.saveGameFile != NULLCHAR) {
11408                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11409                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11410                         else
11411                         SaveGameToFile(appData.saveGameFile, TRUE);
11412                     } else if (appData.autoSaveGames) {
11413                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11414                     }
11415                     if (*appData.savePositionFile != NULLCHAR) {
11416                         SavePositionToFile(appData.savePositionFile);
11417                     }
11418                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11419                 }
11420             }
11421
11422             /* Tell program how game ended in case it is learning */
11423             /* [HGM] Moved this to after saving the PGN, just in case */
11424             /* engine died and we got here through time loss. In that */
11425             /* case we will get a fatal error writing the pipe, which */
11426             /* would otherwise lose us the PGN.                       */
11427             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11428             /* output during GameEnds should never be fatal anymore   */
11429             if (gameMode == MachinePlaysWhite ||
11430                 gameMode == MachinePlaysBlack ||
11431                 gameMode == TwoMachinesPlay ||
11432                 gameMode == IcsPlayingWhite ||
11433                 gameMode == IcsPlayingBlack ||
11434                 gameMode == BeginningOfGame) {
11435                 char buf[MSG_SIZ];
11436                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11437                         resultDetails);
11438                 if (first.pr != NoProc) {
11439                     SendToProgram(buf, &first);
11440                 }
11441                 if (second.pr != NoProc &&
11442                     gameMode == TwoMachinesPlay) {
11443                     SendToProgram(buf, &second);
11444                 }
11445             }
11446         }
11447
11448         if (appData.icsActive) {
11449             if (appData.quietPlay &&
11450                 (gameMode == IcsPlayingWhite ||
11451                  gameMode == IcsPlayingBlack)) {
11452                 SendToICS(ics_prefix);
11453                 SendToICS("set shout 1\n");
11454             }
11455             nextGameMode = IcsIdle;
11456             ics_user_moved = FALSE;
11457             /* clean up premove.  It's ugly when the game has ended and the
11458              * premove highlights are still on the board.
11459              */
11460             if (gotPremove) {
11461               gotPremove = FALSE;
11462               ClearPremoveHighlights();
11463               DrawPosition(FALSE, boards[currentMove]);
11464             }
11465             if (whosays == GE_ICS) {
11466                 switch (result) {
11467                 case WhiteWins:
11468                     if (gameMode == IcsPlayingWhite)
11469                         PlayIcsWinSound();
11470                     else if(gameMode == IcsPlayingBlack)
11471                         PlayIcsLossSound();
11472                     break;
11473                 case BlackWins:
11474                     if (gameMode == IcsPlayingBlack)
11475                         PlayIcsWinSound();
11476                     else if(gameMode == IcsPlayingWhite)
11477                         PlayIcsLossSound();
11478                     break;
11479                 case GameIsDrawn:
11480                     PlayIcsDrawSound();
11481                     break;
11482                 default:
11483                     PlayIcsUnfinishedSound();
11484                 }
11485             }
11486             if(appData.quitNext) { ExitEvent(0); return; }
11487         } else if (gameMode == EditGame ||
11488                    gameMode == PlayFromGameFile ||
11489                    gameMode == AnalyzeMode ||
11490                    gameMode == AnalyzeFile) {
11491             nextGameMode = gameMode;
11492         } else {
11493             nextGameMode = EndOfGame;
11494         }
11495         pausing = FALSE;
11496         ModeHighlight();
11497     } else {
11498         nextGameMode = gameMode;
11499     }
11500
11501     if (appData.noChessProgram) {
11502         gameMode = nextGameMode;
11503         ModeHighlight();
11504         endingGame = 0; /* [HGM] crash */
11505         return;
11506     }
11507
11508     if (first.reuse) {
11509         /* Put first chess program into idle state */
11510         if (first.pr != NoProc &&
11511             (gameMode == MachinePlaysWhite ||
11512              gameMode == MachinePlaysBlack ||
11513              gameMode == TwoMachinesPlay ||
11514              gameMode == IcsPlayingWhite ||
11515              gameMode == IcsPlayingBlack ||
11516              gameMode == BeginningOfGame)) {
11517             SendToProgram("force\n", &first);
11518             if (first.usePing) {
11519               char buf[MSG_SIZ];
11520               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11521               SendToProgram(buf, &first);
11522             }
11523         }
11524     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11525         /* Kill off first chess program */
11526         if (first.isr != NULL)
11527           RemoveInputSource(first.isr);
11528         first.isr = NULL;
11529
11530         if (first.pr != NoProc) {
11531             ExitAnalyzeMode();
11532             DoSleep( appData.delayBeforeQuit );
11533             SendToProgram("quit\n", &first);
11534             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11535             first.reload = TRUE;
11536         }
11537         first.pr = NoProc;
11538     }
11539     if (second.reuse) {
11540         /* Put second chess program into idle state */
11541         if (second.pr != NoProc &&
11542             gameMode == TwoMachinesPlay) {
11543             SendToProgram("force\n", &second);
11544             if (second.usePing) {
11545               char buf[MSG_SIZ];
11546               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11547               SendToProgram(buf, &second);
11548             }
11549         }
11550     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11551         /* Kill off second chess program */
11552         if (second.isr != NULL)
11553           RemoveInputSource(second.isr);
11554         second.isr = NULL;
11555
11556         if (second.pr != NoProc) {
11557             DoSleep( appData.delayBeforeQuit );
11558             SendToProgram("quit\n", &second);
11559             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11560             second.reload = TRUE;
11561         }
11562         second.pr = NoProc;
11563     }
11564
11565     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11566         char resChar = '=';
11567         switch (result) {
11568         case WhiteWins:
11569           resChar = '+';
11570           if (first.twoMachinesColor[0] == 'w') {
11571             first.matchWins++;
11572           } else {
11573             second.matchWins++;
11574           }
11575           break;
11576         case BlackWins:
11577           resChar = '-';
11578           if (first.twoMachinesColor[0] == 'b') {
11579             first.matchWins++;
11580           } else {
11581             second.matchWins++;
11582           }
11583           break;
11584         case GameUnfinished:
11585           resChar = ' ';
11586         default:
11587           break;
11588         }
11589
11590         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11591         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11592             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11593             ReserveGame(nextGame, resChar); // sets nextGame
11594             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11595             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11596         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11597
11598         if (nextGame <= appData.matchGames && !abortMatch) {
11599             gameMode = nextGameMode;
11600             matchGame = nextGame; // this will be overruled in tourney mode!
11601             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11602             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11603             endingGame = 0; /* [HGM] crash */
11604             return;
11605         } else {
11606             gameMode = nextGameMode;
11607             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11608                      first.tidy, second.tidy,
11609                      first.matchWins, second.matchWins,
11610                      appData.matchGames - (first.matchWins + second.matchWins));
11611             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11612             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11613             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11614             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11615                 first.twoMachinesColor = "black\n";
11616                 second.twoMachinesColor = "white\n";
11617             } else {
11618                 first.twoMachinesColor = "white\n";
11619                 second.twoMachinesColor = "black\n";
11620             }
11621         }
11622     }
11623     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11624         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11625       ExitAnalyzeMode();
11626     gameMode = nextGameMode;
11627     ModeHighlight();
11628     endingGame = 0;  /* [HGM] crash */
11629     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11630         if(matchMode == TRUE) { // match through command line: exit with or without popup
11631             if(ranking) {
11632                 ToNrEvent(forwardMostMove);
11633                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11634                 else ExitEvent(0);
11635             } else DisplayFatalError(buf, 0, 0);
11636         } else { // match through menu; just stop, with or without popup
11637             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11638             ModeHighlight();
11639             if(ranking){
11640                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11641             } else DisplayNote(buf);
11642       }
11643       if(ranking) free(ranking);
11644     }
11645 }
11646
11647 /* Assumes program was just initialized (initString sent).
11648    Leaves program in force mode. */
11649 void
11650 FeedMovesToProgram (ChessProgramState *cps, int upto)
11651 {
11652     int i;
11653
11654     if (appData.debugMode)
11655       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11656               startedFromSetupPosition ? "position and " : "",
11657               backwardMostMove, upto, cps->which);
11658     if(currentlyInitializedVariant != gameInfo.variant) {
11659       char buf[MSG_SIZ];
11660         // [HGM] variantswitch: make engine aware of new variant
11661         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11662                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11663                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11664         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11665         SendToProgram(buf, cps);
11666         currentlyInitializedVariant = gameInfo.variant;
11667     }
11668     SendToProgram("force\n", cps);
11669     if (startedFromSetupPosition) {
11670         SendBoard(cps, backwardMostMove);
11671     if (appData.debugMode) {
11672         fprintf(debugFP, "feedMoves\n");
11673     }
11674     }
11675     for (i = backwardMostMove; i < upto; i++) {
11676         SendMoveToProgram(i, cps);
11677     }
11678 }
11679
11680
11681 int
11682 ResurrectChessProgram ()
11683 {
11684      /* The chess program may have exited.
11685         If so, restart it and feed it all the moves made so far. */
11686     static int doInit = 0;
11687
11688     if (appData.noChessProgram) return 1;
11689
11690     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11691         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11692         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11693         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11694     } else {
11695         if (first.pr != NoProc) return 1;
11696         StartChessProgram(&first);
11697     }
11698     InitChessProgram(&first, FALSE);
11699     FeedMovesToProgram(&first, currentMove);
11700
11701     if (!first.sendTime) {
11702         /* can't tell gnuchess what its clock should read,
11703            so we bow to its notion. */
11704         ResetClocks();
11705         timeRemaining[0][currentMove] = whiteTimeRemaining;
11706         timeRemaining[1][currentMove] = blackTimeRemaining;
11707     }
11708
11709     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11710                 appData.icsEngineAnalyze) && first.analysisSupport) {
11711       SendToProgram("analyze\n", &first);
11712       first.analyzing = TRUE;
11713     }
11714     return 1;
11715 }
11716
11717 /*
11718  * Button procedures
11719  */
11720 void
11721 Reset (int redraw, int init)
11722 {
11723     int i;
11724
11725     if (appData.debugMode) {
11726         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11727                 redraw, init, gameMode);
11728     }
11729     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11730     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11731     CleanupTail(); // [HGM] vari: delete any stored variations
11732     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11733     pausing = pauseExamInvalid = FALSE;
11734     startedFromSetupPosition = blackPlaysFirst = FALSE;
11735     firstMove = TRUE;
11736     whiteFlag = blackFlag = FALSE;
11737     userOfferedDraw = FALSE;
11738     hintRequested = bookRequested = FALSE;
11739     first.maybeThinking = FALSE;
11740     second.maybeThinking = FALSE;
11741     first.bookSuspend = FALSE; // [HGM] book
11742     second.bookSuspend = FALSE;
11743     thinkOutput[0] = NULLCHAR;
11744     lastHint[0] = NULLCHAR;
11745     ClearGameInfo(&gameInfo);
11746     gameInfo.variant = StringToVariant(appData.variant);
11747     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11748     ics_user_moved = ics_clock_paused = FALSE;
11749     ics_getting_history = H_FALSE;
11750     ics_gamenum = -1;
11751     white_holding[0] = black_holding[0] = NULLCHAR;
11752     ClearProgramStats();
11753     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11754
11755     ResetFrontEnd();
11756     ClearHighlights();
11757     flipView = appData.flipView;
11758     ClearPremoveHighlights();
11759     gotPremove = FALSE;
11760     alarmSounded = FALSE;
11761     killX = killY = -1; // [HGM] lion
11762
11763     GameEnds(EndOfFile, NULL, GE_PLAYER);
11764     if(appData.serverMovesName != NULL) {
11765         /* [HGM] prepare to make moves file for broadcasting */
11766         clock_t t = clock();
11767         if(serverMoves != NULL) fclose(serverMoves);
11768         serverMoves = fopen(appData.serverMovesName, "r");
11769         if(serverMoves != NULL) {
11770             fclose(serverMoves);
11771             /* delay 15 sec before overwriting, so all clients can see end */
11772             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11773         }
11774         serverMoves = fopen(appData.serverMovesName, "w");
11775     }
11776
11777     ExitAnalyzeMode();
11778     gameMode = BeginningOfGame;
11779     ModeHighlight();
11780     if(appData.icsActive) gameInfo.variant = VariantNormal;
11781     currentMove = forwardMostMove = backwardMostMove = 0;
11782     MarkTargetSquares(1);
11783     InitPosition(redraw);
11784     for (i = 0; i < MAX_MOVES; i++) {
11785         if (commentList[i] != NULL) {
11786             free(commentList[i]);
11787             commentList[i] = NULL;
11788         }
11789     }
11790     ResetClocks();
11791     timeRemaining[0][0] = whiteTimeRemaining;
11792     timeRemaining[1][0] = blackTimeRemaining;
11793
11794     if (first.pr == NoProc) {
11795         StartChessProgram(&first);
11796     }
11797     if (init) {
11798             InitChessProgram(&first, startedFromSetupPosition);
11799     }
11800     DisplayTitle("");
11801     DisplayMessage("", "");
11802     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11803     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11804     ClearMap();        // [HGM] exclude: invalidate map
11805 }
11806
11807 void
11808 AutoPlayGameLoop ()
11809 {
11810     for (;;) {
11811         if (!AutoPlayOneMove())
11812           return;
11813         if (matchMode || appData.timeDelay == 0)
11814           continue;
11815         if (appData.timeDelay < 0)
11816           return;
11817         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11818         break;
11819     }
11820 }
11821
11822 void
11823 AnalyzeNextGame()
11824 {
11825     ReloadGame(1); // next game
11826 }
11827
11828 int
11829 AutoPlayOneMove ()
11830 {
11831     int fromX, fromY, toX, toY;
11832
11833     if (appData.debugMode) {
11834       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11835     }
11836
11837     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11838       return FALSE;
11839
11840     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11841       pvInfoList[currentMove].depth = programStats.depth;
11842       pvInfoList[currentMove].score = programStats.score;
11843       pvInfoList[currentMove].time  = 0;
11844       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11845       else { // append analysis of final position as comment
11846         char buf[MSG_SIZ];
11847         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11848         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11849       }
11850       programStats.depth = 0;
11851     }
11852
11853     if (currentMove >= forwardMostMove) {
11854       if(gameMode == AnalyzeFile) {
11855           if(appData.loadGameIndex == -1) {
11856             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11857           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11858           } else {
11859           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11860         }
11861       }
11862 //      gameMode = EndOfGame;
11863 //      ModeHighlight();
11864
11865       /* [AS] Clear current move marker at the end of a game */
11866       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11867
11868       return FALSE;
11869     }
11870
11871     toX = moveList[currentMove][2] - AAA;
11872     toY = moveList[currentMove][3] - ONE;
11873
11874     if (moveList[currentMove][1] == '@') {
11875         if (appData.highlightLastMove) {
11876             SetHighlights(-1, -1, toX, toY);
11877         }
11878     } else {
11879         int viaX = moveList[currentMove][5] - AAA;
11880         int viaY = moveList[currentMove][6] - ONE;
11881         fromX = moveList[currentMove][0] - AAA;
11882         fromY = moveList[currentMove][1] - ONE;
11883
11884         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11885
11886         if(moveList[currentMove][4] == ';') { // multi-leg
11887             ChessSquare piece = boards[currentMove][viaY][viaX];
11888             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11889             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11890             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11891             boards[currentMove][viaY][viaX] = piece;
11892         } else
11893         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11894
11895         if (appData.highlightLastMove) {
11896             SetHighlights(fromX, fromY, toX, toY);
11897         }
11898     }
11899     DisplayMove(currentMove);
11900     SendMoveToProgram(currentMove++, &first);
11901     DisplayBothClocks();
11902     DrawPosition(FALSE, boards[currentMove]);
11903     // [HGM] PV info: always display, routine tests if empty
11904     DisplayComment(currentMove - 1, commentList[currentMove]);
11905     return TRUE;
11906 }
11907
11908
11909 int
11910 LoadGameOneMove (ChessMove readAhead)
11911 {
11912     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11913     char promoChar = NULLCHAR;
11914     ChessMove moveType;
11915     char move[MSG_SIZ];
11916     char *p, *q;
11917
11918     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11919         gameMode != AnalyzeMode && gameMode != Training) {
11920         gameFileFP = NULL;
11921         return FALSE;
11922     }
11923
11924     yyboardindex = forwardMostMove;
11925     if (readAhead != EndOfFile) {
11926       moveType = readAhead;
11927     } else {
11928       if (gameFileFP == NULL)
11929           return FALSE;
11930       moveType = (ChessMove) Myylex();
11931     }
11932
11933     done = FALSE;
11934     switch (moveType) {
11935       case Comment:
11936         if (appData.debugMode)
11937           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11938         p = yy_text;
11939
11940         /* append the comment but don't display it */
11941         AppendComment(currentMove, p, FALSE);
11942         return TRUE;
11943
11944       case WhiteCapturesEnPassant:
11945       case BlackCapturesEnPassant:
11946       case WhitePromotion:
11947       case BlackPromotion:
11948       case WhiteNonPromotion:
11949       case BlackNonPromotion:
11950       case NormalMove:
11951       case FirstLeg:
11952       case WhiteKingSideCastle:
11953       case WhiteQueenSideCastle:
11954       case BlackKingSideCastle:
11955       case BlackQueenSideCastle:
11956       case WhiteKingSideCastleWild:
11957       case WhiteQueenSideCastleWild:
11958       case BlackKingSideCastleWild:
11959       case BlackQueenSideCastleWild:
11960       /* PUSH Fabien */
11961       case WhiteHSideCastleFR:
11962       case WhiteASideCastleFR:
11963       case BlackHSideCastleFR:
11964       case BlackASideCastleFR:
11965       /* POP Fabien */
11966         if (appData.debugMode)
11967           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11968         fromX = currentMoveString[0] - AAA;
11969         fromY = currentMoveString[1] - ONE;
11970         toX = currentMoveString[2] - AAA;
11971         toY = currentMoveString[3] - ONE;
11972         promoChar = currentMoveString[4];
11973         if(promoChar == ';') promoChar = NULLCHAR;
11974         break;
11975
11976       case WhiteDrop:
11977       case BlackDrop:
11978         if (appData.debugMode)
11979           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11980         fromX = moveType == WhiteDrop ?
11981           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11982         (int) CharToPiece(ToLower(currentMoveString[0]));
11983         fromY = DROP_RANK;
11984         toX = currentMoveString[2] - AAA;
11985         toY = currentMoveString[3] - ONE;
11986         break;
11987
11988       case WhiteWins:
11989       case BlackWins:
11990       case GameIsDrawn:
11991       case GameUnfinished:
11992         if (appData.debugMode)
11993           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11994         p = strchr(yy_text, '{');
11995         if (p == NULL) p = strchr(yy_text, '(');
11996         if (p == NULL) {
11997             p = yy_text;
11998             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11999         } else {
12000             q = strchr(p, *p == '{' ? '}' : ')');
12001             if (q != NULL) *q = NULLCHAR;
12002             p++;
12003         }
12004         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12005         GameEnds(moveType, p, GE_FILE);
12006         done = TRUE;
12007         if (cmailMsgLoaded) {
12008             ClearHighlights();
12009             flipView = WhiteOnMove(currentMove);
12010             if (moveType == GameUnfinished) flipView = !flipView;
12011             if (appData.debugMode)
12012               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12013         }
12014         break;
12015
12016       case EndOfFile:
12017         if (appData.debugMode)
12018           fprintf(debugFP, "Parser hit end of file\n");
12019         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12020           case MT_NONE:
12021           case MT_CHECK:
12022             break;
12023           case MT_CHECKMATE:
12024           case MT_STAINMATE:
12025             if (WhiteOnMove(currentMove)) {
12026                 GameEnds(BlackWins, "Black mates", GE_FILE);
12027             } else {
12028                 GameEnds(WhiteWins, "White mates", GE_FILE);
12029             }
12030             break;
12031           case MT_STALEMATE:
12032             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12033             break;
12034         }
12035         done = TRUE;
12036         break;
12037
12038       case MoveNumberOne:
12039         if (lastLoadGameStart == GNUChessGame) {
12040             /* GNUChessGames have numbers, but they aren't move numbers */
12041             if (appData.debugMode)
12042               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12043                       yy_text, (int) moveType);
12044             return LoadGameOneMove(EndOfFile); /* tail recursion */
12045         }
12046         /* else fall thru */
12047
12048       case XBoardGame:
12049       case GNUChessGame:
12050       case PGNTag:
12051         /* Reached start of next game in file */
12052         if (appData.debugMode)
12053           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12054         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12055           case MT_NONE:
12056           case MT_CHECK:
12057             break;
12058           case MT_CHECKMATE:
12059           case MT_STAINMATE:
12060             if (WhiteOnMove(currentMove)) {
12061                 GameEnds(BlackWins, "Black mates", GE_FILE);
12062             } else {
12063                 GameEnds(WhiteWins, "White mates", GE_FILE);
12064             }
12065             break;
12066           case MT_STALEMATE:
12067             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12068             break;
12069         }
12070         done = TRUE;
12071         break;
12072
12073       case PositionDiagram:     /* should not happen; ignore */
12074       case ElapsedTime:         /* ignore */
12075       case NAG:                 /* ignore */
12076         if (appData.debugMode)
12077           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12078                   yy_text, (int) moveType);
12079         return LoadGameOneMove(EndOfFile); /* tail recursion */
12080
12081       case IllegalMove:
12082         if (appData.testLegality) {
12083             if (appData.debugMode)
12084               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12085             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12086                     (forwardMostMove / 2) + 1,
12087                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12088             DisplayError(move, 0);
12089             done = TRUE;
12090         } else {
12091             if (appData.debugMode)
12092               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12093                       yy_text, currentMoveString);
12094             if(currentMoveString[1] == '@') {
12095                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12096                 fromY = DROP_RANK;
12097             } else {
12098                 fromX = currentMoveString[0] - AAA;
12099                 fromY = currentMoveString[1] - ONE;
12100             }
12101             toX = currentMoveString[2] - AAA;
12102             toY = currentMoveString[3] - ONE;
12103             promoChar = currentMoveString[4];
12104         }
12105         break;
12106
12107       case AmbiguousMove:
12108         if (appData.debugMode)
12109           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12110         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12111                 (forwardMostMove / 2) + 1,
12112                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12113         DisplayError(move, 0);
12114         done = TRUE;
12115         break;
12116
12117       default:
12118       case ImpossibleMove:
12119         if (appData.debugMode)
12120           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12121         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12122                 (forwardMostMove / 2) + 1,
12123                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12124         DisplayError(move, 0);
12125         done = TRUE;
12126         break;
12127     }
12128
12129     if (done) {
12130         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12131             DrawPosition(FALSE, boards[currentMove]);
12132             DisplayBothClocks();
12133             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12134               DisplayComment(currentMove - 1, commentList[currentMove]);
12135         }
12136         (void) StopLoadGameTimer();
12137         gameFileFP = NULL;
12138         cmailOldMove = forwardMostMove;
12139         return FALSE;
12140     } else {
12141         /* currentMoveString is set as a side-effect of yylex */
12142
12143         thinkOutput[0] = NULLCHAR;
12144         MakeMove(fromX, fromY, toX, toY, promoChar);
12145         killX = killY = -1; // [HGM] lion: used up
12146         currentMove = forwardMostMove;
12147         return TRUE;
12148     }
12149 }
12150
12151 /* Load the nth game from the given file */
12152 int
12153 LoadGameFromFile (char *filename, int n, char *title, int useList)
12154 {
12155     FILE *f;
12156     char buf[MSG_SIZ];
12157
12158     if (strcmp(filename, "-") == 0) {
12159         f = stdin;
12160         title = "stdin";
12161     } else {
12162         f = fopen(filename, "rb");
12163         if (f == NULL) {
12164           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12165             DisplayError(buf, errno);
12166             return FALSE;
12167         }
12168     }
12169     if (fseek(f, 0, 0) == -1) {
12170         /* f is not seekable; probably a pipe */
12171         useList = FALSE;
12172     }
12173     if (useList && n == 0) {
12174         int error = GameListBuild(f);
12175         if (error) {
12176             DisplayError(_("Cannot build game list"), error);
12177         } else if (!ListEmpty(&gameList) &&
12178                    ((ListGame *) gameList.tailPred)->number > 1) {
12179             GameListPopUp(f, title);
12180             return TRUE;
12181         }
12182         GameListDestroy();
12183         n = 1;
12184     }
12185     if (n == 0) n = 1;
12186     return LoadGame(f, n, title, FALSE);
12187 }
12188
12189
12190 void
12191 MakeRegisteredMove ()
12192 {
12193     int fromX, fromY, toX, toY;
12194     char promoChar;
12195     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12196         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12197           case CMAIL_MOVE:
12198           case CMAIL_DRAW:
12199             if (appData.debugMode)
12200               fprintf(debugFP, "Restoring %s for game %d\n",
12201                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12202
12203             thinkOutput[0] = NULLCHAR;
12204             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12205             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12206             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12207             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12208             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12209             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12210             MakeMove(fromX, fromY, toX, toY, promoChar);
12211             ShowMove(fromX, fromY, toX, toY);
12212
12213             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12214               case MT_NONE:
12215               case MT_CHECK:
12216                 break;
12217
12218               case MT_CHECKMATE:
12219               case MT_STAINMATE:
12220                 if (WhiteOnMove(currentMove)) {
12221                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12222                 } else {
12223                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12224                 }
12225                 break;
12226
12227               case MT_STALEMATE:
12228                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12229                 break;
12230             }
12231
12232             break;
12233
12234           case CMAIL_RESIGN:
12235             if (WhiteOnMove(currentMove)) {
12236                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12237             } else {
12238                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12239             }
12240             break;
12241
12242           case CMAIL_ACCEPT:
12243             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12244             break;
12245
12246           default:
12247             break;
12248         }
12249     }
12250
12251     return;
12252 }
12253
12254 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12255 int
12256 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12257 {
12258     int retVal;
12259
12260     if (gameNumber > nCmailGames) {
12261         DisplayError(_("No more games in this message"), 0);
12262         return FALSE;
12263     }
12264     if (f == lastLoadGameFP) {
12265         int offset = gameNumber - lastLoadGameNumber;
12266         if (offset == 0) {
12267             cmailMsg[0] = NULLCHAR;
12268             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12269                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12270                 nCmailMovesRegistered--;
12271             }
12272             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12273             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12274                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12275             }
12276         } else {
12277             if (! RegisterMove()) return FALSE;
12278         }
12279     }
12280
12281     retVal = LoadGame(f, gameNumber, title, useList);
12282
12283     /* Make move registered during previous look at this game, if any */
12284     MakeRegisteredMove();
12285
12286     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12287         commentList[currentMove]
12288           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12289         DisplayComment(currentMove - 1, commentList[currentMove]);
12290     }
12291
12292     return retVal;
12293 }
12294
12295 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12296 int
12297 ReloadGame (int offset)
12298 {
12299     int gameNumber = lastLoadGameNumber + offset;
12300     if (lastLoadGameFP == NULL) {
12301         DisplayError(_("No game has been loaded yet"), 0);
12302         return FALSE;
12303     }
12304     if (gameNumber <= 0) {
12305         DisplayError(_("Can't back up any further"), 0);
12306         return FALSE;
12307     }
12308     if (cmailMsgLoaded) {
12309         return CmailLoadGame(lastLoadGameFP, gameNumber,
12310                              lastLoadGameTitle, lastLoadGameUseList);
12311     } else {
12312         return LoadGame(lastLoadGameFP, gameNumber,
12313                         lastLoadGameTitle, lastLoadGameUseList);
12314     }
12315 }
12316
12317 int keys[EmptySquare+1];
12318
12319 int
12320 PositionMatches (Board b1, Board b2)
12321 {
12322     int r, f, sum=0;
12323     switch(appData.searchMode) {
12324         case 1: return CompareWithRights(b1, b2);
12325         case 2:
12326             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12327                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12328             }
12329             return TRUE;
12330         case 3:
12331             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12332               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12333                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12334             }
12335             return sum==0;
12336         case 4:
12337             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12338                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12339             }
12340             return sum==0;
12341     }
12342     return TRUE;
12343 }
12344
12345 #define Q_PROMO  4
12346 #define Q_EP     3
12347 #define Q_BCASTL 2
12348 #define Q_WCASTL 1
12349
12350 int pieceList[256], quickBoard[256];
12351 ChessSquare pieceType[256] = { EmptySquare };
12352 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12353 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12354 int soughtTotal, turn;
12355 Boolean epOK, flipSearch;
12356
12357 typedef struct {
12358     unsigned char piece, to;
12359 } Move;
12360
12361 #define DSIZE (250000)
12362
12363 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12364 Move *moveDatabase = initialSpace;
12365 unsigned int movePtr, dataSize = DSIZE;
12366
12367 int
12368 MakePieceList (Board board, int *counts)
12369 {
12370     int r, f, n=Q_PROMO, total=0;
12371     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12372     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12373         int sq = f + (r<<4);
12374         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12375             quickBoard[sq] = ++n;
12376             pieceList[n] = sq;
12377             pieceType[n] = board[r][f];
12378             counts[board[r][f]]++;
12379             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12380             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12381             total++;
12382         }
12383     }
12384     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12385     return total;
12386 }
12387
12388 void
12389 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12390 {
12391     int sq = fromX + (fromY<<4);
12392     int piece = quickBoard[sq], rook;
12393     quickBoard[sq] = 0;
12394     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12395     if(piece == pieceList[1] && fromY == toY) {
12396       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12397         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12398         moveDatabase[movePtr++].piece = Q_WCASTL;
12399         quickBoard[sq] = piece;
12400         piece = quickBoard[from]; quickBoard[from] = 0;
12401         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12402       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12403         quickBoard[sq] = 0; // remove Rook
12404         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12405         moveDatabase[movePtr++].piece = Q_WCASTL;
12406         quickBoard[sq] = pieceList[1]; // put King
12407         piece = rook;
12408         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12409       }
12410     } else
12411     if(piece == pieceList[2] && fromY == toY) {
12412       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12413         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12414         moveDatabase[movePtr++].piece = Q_BCASTL;
12415         quickBoard[sq] = piece;
12416         piece = quickBoard[from]; quickBoard[from] = 0;
12417         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12418       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12419         quickBoard[sq] = 0; // remove Rook
12420         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12421         moveDatabase[movePtr++].piece = Q_BCASTL;
12422         quickBoard[sq] = pieceList[2]; // put King
12423         piece = rook;
12424         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12425       }
12426     } else
12427     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12428         quickBoard[(fromY<<4)+toX] = 0;
12429         moveDatabase[movePtr].piece = Q_EP;
12430         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12431         moveDatabase[movePtr].to = sq;
12432     } else
12433     if(promoPiece != pieceType[piece]) {
12434         moveDatabase[movePtr++].piece = Q_PROMO;
12435         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12436     }
12437     moveDatabase[movePtr].piece = piece;
12438     quickBoard[sq] = piece;
12439     movePtr++;
12440 }
12441
12442 int
12443 PackGame (Board board)
12444 {
12445     Move *newSpace = NULL;
12446     moveDatabase[movePtr].piece = 0; // terminate previous game
12447     if(movePtr > dataSize) {
12448         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12449         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12450         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12451         if(newSpace) {
12452             int i;
12453             Move *p = moveDatabase, *q = newSpace;
12454             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12455             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12456             moveDatabase = newSpace;
12457         } else { // calloc failed, we must be out of memory. Too bad...
12458             dataSize = 0; // prevent calloc events for all subsequent games
12459             return 0;     // and signal this one isn't cached
12460         }
12461     }
12462     movePtr++;
12463     MakePieceList(board, counts);
12464     return movePtr;
12465 }
12466
12467 int
12468 QuickCompare (Board board, int *minCounts, int *maxCounts)
12469 {   // compare according to search mode
12470     int r, f;
12471     switch(appData.searchMode)
12472     {
12473       case 1: // exact position match
12474         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12475         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12476             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12477         }
12478         break;
12479       case 2: // can have extra material on empty squares
12480         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12481             if(board[r][f] == EmptySquare) continue;
12482             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12483         }
12484         break;
12485       case 3: // material with exact Pawn structure
12486         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12487             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12488             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12489         } // fall through to material comparison
12490       case 4: // exact material
12491         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12492         break;
12493       case 6: // material range with given imbalance
12494         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12495         // fall through to range comparison
12496       case 5: // material range
12497         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12498     }
12499     return TRUE;
12500 }
12501
12502 int
12503 QuickScan (Board board, Move *move)
12504 {   // reconstruct game,and compare all positions in it
12505     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12506     do {
12507         int piece = move->piece;
12508         int to = move->to, from = pieceList[piece];
12509         if(found < 0) { // if already found just scan to game end for final piece count
12510           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12511            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12512            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12513                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12514             ) {
12515             static int lastCounts[EmptySquare+1];
12516             int i;
12517             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12518             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12519           } else stretch = 0;
12520           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12521           if(found >= 0 && !appData.minPieces) return found;
12522         }
12523         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12524           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12525           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12526             piece = (++move)->piece;
12527             from = pieceList[piece];
12528             counts[pieceType[piece]]--;
12529             pieceType[piece] = (ChessSquare) move->to;
12530             counts[move->to]++;
12531           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12532             counts[pieceType[quickBoard[to]]]--;
12533             quickBoard[to] = 0; total--;
12534             move++;
12535             continue;
12536           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12537             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12538             from  = pieceList[piece]; // so this must be King
12539             quickBoard[from] = 0;
12540             pieceList[piece] = to;
12541             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12542             quickBoard[from] = 0; // rook
12543             quickBoard[to] = piece;
12544             to = move->to; piece = move->piece;
12545             goto aftercastle;
12546           }
12547         }
12548         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12549         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12550         quickBoard[from] = 0;
12551       aftercastle:
12552         quickBoard[to] = piece;
12553         pieceList[piece] = to;
12554         cnt++; turn ^= 3;
12555         move++;
12556     } while(1);
12557 }
12558
12559 void
12560 InitSearch ()
12561 {
12562     int r, f;
12563     flipSearch = FALSE;
12564     CopyBoard(soughtBoard, boards[currentMove]);
12565     soughtTotal = MakePieceList(soughtBoard, maxSought);
12566     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12567     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12568     CopyBoard(reverseBoard, boards[currentMove]);
12569     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12570         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12571         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12572         reverseBoard[r][f] = piece;
12573     }
12574     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12575     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12576     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12577                  || (boards[currentMove][CASTLING][2] == NoRights ||
12578                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12579                  && (boards[currentMove][CASTLING][5] == NoRights ||
12580                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12581       ) {
12582         flipSearch = TRUE;
12583         CopyBoard(flipBoard, soughtBoard);
12584         CopyBoard(rotateBoard, reverseBoard);
12585         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12586             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12587             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12588         }
12589     }
12590     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12591     if(appData.searchMode >= 5) {
12592         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12593         MakePieceList(soughtBoard, minSought);
12594         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12595     }
12596     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12597         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12598 }
12599
12600 GameInfo dummyInfo;
12601 static int creatingBook;
12602
12603 int
12604 GameContainsPosition (FILE *f, ListGame *lg)
12605 {
12606     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12607     int fromX, fromY, toX, toY;
12608     char promoChar;
12609     static int initDone=FALSE;
12610
12611     // weed out games based on numerical tag comparison
12612     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12613     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12614     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12615     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12616     if(!initDone) {
12617         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12618         initDone = TRUE;
12619     }
12620     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12621     else CopyBoard(boards[scratch], initialPosition); // default start position
12622     if(lg->moves) {
12623         turn = btm + 1;
12624         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12625         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12626     }
12627     if(btm) plyNr++;
12628     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12629     fseek(f, lg->offset, 0);
12630     yynewfile(f);
12631     while(1) {
12632         yyboardindex = scratch;
12633         quickFlag = plyNr+1;
12634         next = Myylex();
12635         quickFlag = 0;
12636         switch(next) {
12637             case PGNTag:
12638                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12639             default:
12640                 continue;
12641
12642             case XBoardGame:
12643             case GNUChessGame:
12644                 if(plyNr) return -1; // after we have seen moves, this is for new game
12645               continue;
12646
12647             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12648             case ImpossibleMove:
12649             case WhiteWins: // game ends here with these four
12650             case BlackWins:
12651             case GameIsDrawn:
12652             case GameUnfinished:
12653                 return -1;
12654
12655             case IllegalMove:
12656                 if(appData.testLegality) return -1;
12657             case WhiteCapturesEnPassant:
12658             case BlackCapturesEnPassant:
12659             case WhitePromotion:
12660             case BlackPromotion:
12661             case WhiteNonPromotion:
12662             case BlackNonPromotion:
12663             case NormalMove:
12664             case FirstLeg:
12665             case WhiteKingSideCastle:
12666             case WhiteQueenSideCastle:
12667             case BlackKingSideCastle:
12668             case BlackQueenSideCastle:
12669             case WhiteKingSideCastleWild:
12670             case WhiteQueenSideCastleWild:
12671             case BlackKingSideCastleWild:
12672             case BlackQueenSideCastleWild:
12673             case WhiteHSideCastleFR:
12674             case WhiteASideCastleFR:
12675             case BlackHSideCastleFR:
12676             case BlackASideCastleFR:
12677                 fromX = currentMoveString[0] - AAA;
12678                 fromY = currentMoveString[1] - ONE;
12679                 toX = currentMoveString[2] - AAA;
12680                 toY = currentMoveString[3] - ONE;
12681                 promoChar = currentMoveString[4];
12682                 break;
12683             case WhiteDrop:
12684             case BlackDrop:
12685                 fromX = next == WhiteDrop ?
12686                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12687                   (int) CharToPiece(ToLower(currentMoveString[0]));
12688                 fromY = DROP_RANK;
12689                 toX = currentMoveString[2] - AAA;
12690                 toY = currentMoveString[3] - ONE;
12691                 promoChar = 0;
12692                 break;
12693         }
12694         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12695         plyNr++;
12696         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12697         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12698         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12699         if(appData.findMirror) {
12700             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12701             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12702         }
12703     }
12704 }
12705
12706 /* Load the nth game from open file f */
12707 int
12708 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12709 {
12710     ChessMove cm;
12711     char buf[MSG_SIZ];
12712     int gn = gameNumber;
12713     ListGame *lg = NULL;
12714     int numPGNTags = 0;
12715     int err, pos = -1;
12716     GameMode oldGameMode;
12717     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12718     char oldName[MSG_SIZ];
12719
12720     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12721
12722     if (appData.debugMode)
12723         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12724
12725     if (gameMode == Training )
12726         SetTrainingModeOff();
12727
12728     oldGameMode = gameMode;
12729     if (gameMode != BeginningOfGame) {
12730       Reset(FALSE, TRUE);
12731     }
12732     killX = killY = -1; // [HGM] lion: in case we did not Reset
12733
12734     gameFileFP = f;
12735     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12736         fclose(lastLoadGameFP);
12737     }
12738
12739     if (useList) {
12740         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12741
12742         if (lg) {
12743             fseek(f, lg->offset, 0);
12744             GameListHighlight(gameNumber);
12745             pos = lg->position;
12746             gn = 1;
12747         }
12748         else {
12749             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12750               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12751             else
12752             DisplayError(_("Game number out of range"), 0);
12753             return FALSE;
12754         }
12755     } else {
12756         GameListDestroy();
12757         if (fseek(f, 0, 0) == -1) {
12758             if (f == lastLoadGameFP ?
12759                 gameNumber == lastLoadGameNumber + 1 :
12760                 gameNumber == 1) {
12761                 gn = 1;
12762             } else {
12763                 DisplayError(_("Can't seek on game file"), 0);
12764                 return FALSE;
12765             }
12766         }
12767     }
12768     lastLoadGameFP = f;
12769     lastLoadGameNumber = gameNumber;
12770     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12771     lastLoadGameUseList = useList;
12772
12773     yynewfile(f);
12774
12775     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12776       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12777                 lg->gameInfo.black);
12778             DisplayTitle(buf);
12779     } else if (*title != NULLCHAR) {
12780         if (gameNumber > 1) {
12781           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12782             DisplayTitle(buf);
12783         } else {
12784             DisplayTitle(title);
12785         }
12786     }
12787
12788     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12789         gameMode = PlayFromGameFile;
12790         ModeHighlight();
12791     }
12792
12793     currentMove = forwardMostMove = backwardMostMove = 0;
12794     CopyBoard(boards[0], initialPosition);
12795     StopClocks();
12796
12797     /*
12798      * Skip the first gn-1 games in the file.
12799      * Also skip over anything that precedes an identifiable
12800      * start of game marker, to avoid being confused by
12801      * garbage at the start of the file.  Currently
12802      * recognized start of game markers are the move number "1",
12803      * the pattern "gnuchess .* game", the pattern
12804      * "^[#;%] [^ ]* game file", and a PGN tag block.
12805      * A game that starts with one of the latter two patterns
12806      * will also have a move number 1, possibly
12807      * following a position diagram.
12808      * 5-4-02: Let's try being more lenient and allowing a game to
12809      * start with an unnumbered move.  Does that break anything?
12810      */
12811     cm = lastLoadGameStart = EndOfFile;
12812     while (gn > 0) {
12813         yyboardindex = forwardMostMove;
12814         cm = (ChessMove) Myylex();
12815         switch (cm) {
12816           case EndOfFile:
12817             if (cmailMsgLoaded) {
12818                 nCmailGames = CMAIL_MAX_GAMES - gn;
12819             } else {
12820                 Reset(TRUE, TRUE);
12821                 DisplayError(_("Game not found in file"), 0);
12822             }
12823             return FALSE;
12824
12825           case GNUChessGame:
12826           case XBoardGame:
12827             gn--;
12828             lastLoadGameStart = cm;
12829             break;
12830
12831           case MoveNumberOne:
12832             switch (lastLoadGameStart) {
12833               case GNUChessGame:
12834               case XBoardGame:
12835               case PGNTag:
12836                 break;
12837               case MoveNumberOne:
12838               case EndOfFile:
12839                 gn--;           /* count this game */
12840                 lastLoadGameStart = cm;
12841                 break;
12842               default:
12843                 /* impossible */
12844                 break;
12845             }
12846             break;
12847
12848           case PGNTag:
12849             switch (lastLoadGameStart) {
12850               case GNUChessGame:
12851               case PGNTag:
12852               case MoveNumberOne:
12853               case EndOfFile:
12854                 gn--;           /* count this game */
12855                 lastLoadGameStart = cm;
12856                 break;
12857               case XBoardGame:
12858                 lastLoadGameStart = cm; /* game counted already */
12859                 break;
12860               default:
12861                 /* impossible */
12862                 break;
12863             }
12864             if (gn > 0) {
12865                 do {
12866                     yyboardindex = forwardMostMove;
12867                     cm = (ChessMove) Myylex();
12868                 } while (cm == PGNTag || cm == Comment);
12869             }
12870             break;
12871
12872           case WhiteWins:
12873           case BlackWins:
12874           case GameIsDrawn:
12875             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12876                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12877                     != CMAIL_OLD_RESULT) {
12878                     nCmailResults ++ ;
12879                     cmailResult[  CMAIL_MAX_GAMES
12880                                 - gn - 1] = CMAIL_OLD_RESULT;
12881                 }
12882             }
12883             break;
12884
12885           case NormalMove:
12886           case FirstLeg:
12887             /* Only a NormalMove can be at the start of a game
12888              * without a position diagram. */
12889             if (lastLoadGameStart == EndOfFile ) {
12890               gn--;
12891               lastLoadGameStart = MoveNumberOne;
12892             }
12893             break;
12894
12895           default:
12896             break;
12897         }
12898     }
12899
12900     if (appData.debugMode)
12901       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12902
12903     if (cm == XBoardGame) {
12904         /* Skip any header junk before position diagram and/or move 1 */
12905         for (;;) {
12906             yyboardindex = forwardMostMove;
12907             cm = (ChessMove) Myylex();
12908
12909             if (cm == EndOfFile ||
12910                 cm == GNUChessGame || cm == XBoardGame) {
12911                 /* Empty game; pretend end-of-file and handle later */
12912                 cm = EndOfFile;
12913                 break;
12914             }
12915
12916             if (cm == MoveNumberOne || cm == PositionDiagram ||
12917                 cm == PGNTag || cm == Comment)
12918               break;
12919         }
12920     } else if (cm == GNUChessGame) {
12921         if (gameInfo.event != NULL) {
12922             free(gameInfo.event);
12923         }
12924         gameInfo.event = StrSave(yy_text);
12925     }
12926
12927     startedFromSetupPosition = FALSE;
12928     while (cm == PGNTag) {
12929         if (appData.debugMode)
12930           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12931         err = ParsePGNTag(yy_text, &gameInfo);
12932         if (!err) numPGNTags++;
12933
12934         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12935         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12936             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12937             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12938             InitPosition(TRUE);
12939             oldVariant = gameInfo.variant;
12940             if (appData.debugMode)
12941               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12942         }
12943
12944
12945         if (gameInfo.fen != NULL) {
12946           Board initial_position;
12947           startedFromSetupPosition = TRUE;
12948           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12949             Reset(TRUE, TRUE);
12950             DisplayError(_("Bad FEN position in file"), 0);
12951             return FALSE;
12952           }
12953           CopyBoard(boards[0], initial_position);
12954           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12955             CopyBoard(initialPosition, initial_position);
12956           if (blackPlaysFirst) {
12957             currentMove = forwardMostMove = backwardMostMove = 1;
12958             CopyBoard(boards[1], initial_position);
12959             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12960             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12961             timeRemaining[0][1] = whiteTimeRemaining;
12962             timeRemaining[1][1] = blackTimeRemaining;
12963             if (commentList[0] != NULL) {
12964               commentList[1] = commentList[0];
12965               commentList[0] = NULL;
12966             }
12967           } else {
12968             currentMove = forwardMostMove = backwardMostMove = 0;
12969           }
12970           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12971           {   int i;
12972               initialRulePlies = FENrulePlies;
12973               for( i=0; i< nrCastlingRights; i++ )
12974                   initialRights[i] = initial_position[CASTLING][i];
12975           }
12976           yyboardindex = forwardMostMove;
12977           free(gameInfo.fen);
12978           gameInfo.fen = NULL;
12979         }
12980
12981         yyboardindex = forwardMostMove;
12982         cm = (ChessMove) Myylex();
12983
12984         /* Handle comments interspersed among the tags */
12985         while (cm == Comment) {
12986             char *p;
12987             if (appData.debugMode)
12988               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12989             p = yy_text;
12990             AppendComment(currentMove, p, FALSE);
12991             yyboardindex = forwardMostMove;
12992             cm = (ChessMove) Myylex();
12993         }
12994     }
12995
12996     /* don't rely on existence of Event tag since if game was
12997      * pasted from clipboard the Event tag may not exist
12998      */
12999     if (numPGNTags > 0){
13000         char *tags;
13001         if (gameInfo.variant == VariantNormal) {
13002           VariantClass v = StringToVariant(gameInfo.event);
13003           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13004           if(v < VariantShogi) gameInfo.variant = v;
13005         }
13006         if (!matchMode) {
13007           if( appData.autoDisplayTags ) {
13008             tags = PGNTags(&gameInfo);
13009             TagsPopUp(tags, CmailMsg());
13010             free(tags);
13011           }
13012         }
13013     } else {
13014         /* Make something up, but don't display it now */
13015         SetGameInfo();
13016         TagsPopDown();
13017     }
13018
13019     if (cm == PositionDiagram) {
13020         int i, j;
13021         char *p;
13022         Board initial_position;
13023
13024         if (appData.debugMode)
13025           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13026
13027         if (!startedFromSetupPosition) {
13028             p = yy_text;
13029             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13030               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13031                 switch (*p) {
13032                   case '{':
13033                   case '[':
13034                   case '-':
13035                   case ' ':
13036                   case '\t':
13037                   case '\n':
13038                   case '\r':
13039                     break;
13040                   default:
13041                     initial_position[i][j++] = CharToPiece(*p);
13042                     break;
13043                 }
13044             while (*p == ' ' || *p == '\t' ||
13045                    *p == '\n' || *p == '\r') p++;
13046
13047             if (strncmp(p, "black", strlen("black"))==0)
13048               blackPlaysFirst = TRUE;
13049             else
13050               blackPlaysFirst = FALSE;
13051             startedFromSetupPosition = TRUE;
13052
13053             CopyBoard(boards[0], initial_position);
13054             if (blackPlaysFirst) {
13055                 currentMove = forwardMostMove = backwardMostMove = 1;
13056                 CopyBoard(boards[1], initial_position);
13057                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13058                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13059                 timeRemaining[0][1] = whiteTimeRemaining;
13060                 timeRemaining[1][1] = blackTimeRemaining;
13061                 if (commentList[0] != NULL) {
13062                     commentList[1] = commentList[0];
13063                     commentList[0] = NULL;
13064                 }
13065             } else {
13066                 currentMove = forwardMostMove = backwardMostMove = 0;
13067             }
13068         }
13069         yyboardindex = forwardMostMove;
13070         cm = (ChessMove) Myylex();
13071     }
13072
13073   if(!creatingBook) {
13074     if (first.pr == NoProc) {
13075         StartChessProgram(&first);
13076     }
13077     InitChessProgram(&first, FALSE);
13078     if(gameInfo.variant == VariantUnknown && *oldName) {
13079         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13080         gameInfo.variant = v;
13081     }
13082     SendToProgram("force\n", &first);
13083     if (startedFromSetupPosition) {
13084         SendBoard(&first, forwardMostMove);
13085     if (appData.debugMode) {
13086         fprintf(debugFP, "Load Game\n");
13087     }
13088         DisplayBothClocks();
13089     }
13090   }
13091
13092     /* [HGM] server: flag to write setup moves in broadcast file as one */
13093     loadFlag = appData.suppressLoadMoves;
13094
13095     while (cm == Comment) {
13096         char *p;
13097         if (appData.debugMode)
13098           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13099         p = yy_text;
13100         AppendComment(currentMove, p, FALSE);
13101         yyboardindex = forwardMostMove;
13102         cm = (ChessMove) Myylex();
13103     }
13104
13105     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13106         cm == WhiteWins || cm == BlackWins ||
13107         cm == GameIsDrawn || cm == GameUnfinished) {
13108         DisplayMessage("", _("No moves in game"));
13109         if (cmailMsgLoaded) {
13110             if (appData.debugMode)
13111               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13112             ClearHighlights();
13113             flipView = FALSE;
13114         }
13115         DrawPosition(FALSE, boards[currentMove]);
13116         DisplayBothClocks();
13117         gameMode = EditGame;
13118         ModeHighlight();
13119         gameFileFP = NULL;
13120         cmailOldMove = 0;
13121         return TRUE;
13122     }
13123
13124     // [HGM] PV info: routine tests if comment empty
13125     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13126         DisplayComment(currentMove - 1, commentList[currentMove]);
13127     }
13128     if (!matchMode && appData.timeDelay != 0)
13129       DrawPosition(FALSE, boards[currentMove]);
13130
13131     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13132       programStats.ok_to_send = 1;
13133     }
13134
13135     /* if the first token after the PGN tags is a move
13136      * and not move number 1, retrieve it from the parser
13137      */
13138     if (cm != MoveNumberOne)
13139         LoadGameOneMove(cm);
13140
13141     /* load the remaining moves from the file */
13142     while (LoadGameOneMove(EndOfFile)) {
13143       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13144       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13145     }
13146
13147     /* rewind to the start of the game */
13148     currentMove = backwardMostMove;
13149
13150     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13151
13152     if (oldGameMode == AnalyzeFile) {
13153       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13154       AnalyzeFileEvent();
13155     } else
13156     if (oldGameMode == AnalyzeMode) {
13157       AnalyzeFileEvent();
13158     }
13159
13160     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13161         long int w, b; // [HGM] adjourn: restore saved clock times
13162         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13163         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13164             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13165             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13166         }
13167     }
13168
13169     if(creatingBook) return TRUE;
13170     if (!matchMode && pos > 0) {
13171         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13172     } else
13173     if (matchMode || appData.timeDelay == 0) {
13174       ToEndEvent();
13175     } else if (appData.timeDelay > 0) {
13176       AutoPlayGameLoop();
13177     }
13178
13179     if (appData.debugMode)
13180         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13181
13182     loadFlag = 0; /* [HGM] true game starts */
13183     return TRUE;
13184 }
13185
13186 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13187 int
13188 ReloadPosition (int offset)
13189 {
13190     int positionNumber = lastLoadPositionNumber + offset;
13191     if (lastLoadPositionFP == NULL) {
13192         DisplayError(_("No position has been loaded yet"), 0);
13193         return FALSE;
13194     }
13195     if (positionNumber <= 0) {
13196         DisplayError(_("Can't back up any further"), 0);
13197         return FALSE;
13198     }
13199     return LoadPosition(lastLoadPositionFP, positionNumber,
13200                         lastLoadPositionTitle);
13201 }
13202
13203 /* Load the nth position from the given file */
13204 int
13205 LoadPositionFromFile (char *filename, int n, char *title)
13206 {
13207     FILE *f;
13208     char buf[MSG_SIZ];
13209
13210     if (strcmp(filename, "-") == 0) {
13211         return LoadPosition(stdin, n, "stdin");
13212     } else {
13213         f = fopen(filename, "rb");
13214         if (f == NULL) {
13215             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13216             DisplayError(buf, errno);
13217             return FALSE;
13218         } else {
13219             return LoadPosition(f, n, title);
13220         }
13221     }
13222 }
13223
13224 /* Load the nth position from the given open file, and close it */
13225 int
13226 LoadPosition (FILE *f, int positionNumber, char *title)
13227 {
13228     char *p, line[MSG_SIZ];
13229     Board initial_position;
13230     int i, j, fenMode, pn;
13231
13232     if (gameMode == Training )
13233         SetTrainingModeOff();
13234
13235     if (gameMode != BeginningOfGame) {
13236         Reset(FALSE, TRUE);
13237     }
13238     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13239         fclose(lastLoadPositionFP);
13240     }
13241     if (positionNumber == 0) positionNumber = 1;
13242     lastLoadPositionFP = f;
13243     lastLoadPositionNumber = positionNumber;
13244     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13245     if (first.pr == NoProc && !appData.noChessProgram) {
13246       StartChessProgram(&first);
13247       InitChessProgram(&first, FALSE);
13248     }
13249     pn = positionNumber;
13250     if (positionNumber < 0) {
13251         /* Negative position number means to seek to that byte offset */
13252         if (fseek(f, -positionNumber, 0) == -1) {
13253             DisplayError(_("Can't seek on position file"), 0);
13254             return FALSE;
13255         };
13256         pn = 1;
13257     } else {
13258         if (fseek(f, 0, 0) == -1) {
13259             if (f == lastLoadPositionFP ?
13260                 positionNumber == lastLoadPositionNumber + 1 :
13261                 positionNumber == 1) {
13262                 pn = 1;
13263             } else {
13264                 DisplayError(_("Can't seek on position file"), 0);
13265                 return FALSE;
13266             }
13267         }
13268     }
13269     /* See if this file is FEN or old-style xboard */
13270     if (fgets(line, MSG_SIZ, f) == NULL) {
13271         DisplayError(_("Position not found in file"), 0);
13272         return FALSE;
13273     }
13274     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13275     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13276
13277     if (pn >= 2) {
13278         if (fenMode || line[0] == '#') pn--;
13279         while (pn > 0) {
13280             /* skip positions before number pn */
13281             if (fgets(line, MSG_SIZ, f) == NULL) {
13282                 Reset(TRUE, TRUE);
13283                 DisplayError(_("Position not found in file"), 0);
13284                 return FALSE;
13285             }
13286             if (fenMode || line[0] == '#') pn--;
13287         }
13288     }
13289
13290     if (fenMode) {
13291         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13292             DisplayError(_("Bad FEN position in file"), 0);
13293             return FALSE;
13294         }
13295     } else {
13296         (void) fgets(line, MSG_SIZ, f);
13297         (void) fgets(line, MSG_SIZ, f);
13298
13299         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13300             (void) fgets(line, MSG_SIZ, f);
13301             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13302                 if (*p == ' ')
13303                   continue;
13304                 initial_position[i][j++] = CharToPiece(*p);
13305             }
13306         }
13307
13308         blackPlaysFirst = FALSE;
13309         if (!feof(f)) {
13310             (void) fgets(line, MSG_SIZ, f);
13311             if (strncmp(line, "black", strlen("black"))==0)
13312               blackPlaysFirst = TRUE;
13313         }
13314     }
13315     startedFromSetupPosition = TRUE;
13316
13317     CopyBoard(boards[0], initial_position);
13318     if (blackPlaysFirst) {
13319         currentMove = forwardMostMove = backwardMostMove = 1;
13320         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13321         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13322         CopyBoard(boards[1], initial_position);
13323         DisplayMessage("", _("Black to play"));
13324     } else {
13325         currentMove = forwardMostMove = backwardMostMove = 0;
13326         DisplayMessage("", _("White to play"));
13327     }
13328     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13329     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13330         SendToProgram("force\n", &first);
13331         SendBoard(&first, forwardMostMove);
13332     }
13333     if (appData.debugMode) {
13334 int i, j;
13335   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13336   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13337         fprintf(debugFP, "Load Position\n");
13338     }
13339
13340     if (positionNumber > 1) {
13341       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13342         DisplayTitle(line);
13343     } else {
13344         DisplayTitle(title);
13345     }
13346     gameMode = EditGame;
13347     ModeHighlight();
13348     ResetClocks();
13349     timeRemaining[0][1] = whiteTimeRemaining;
13350     timeRemaining[1][1] = blackTimeRemaining;
13351     DrawPosition(FALSE, boards[currentMove]);
13352
13353     return TRUE;
13354 }
13355
13356
13357 void
13358 CopyPlayerNameIntoFileName (char **dest, char *src)
13359 {
13360     while (*src != NULLCHAR && *src != ',') {
13361         if (*src == ' ') {
13362             *(*dest)++ = '_';
13363             src++;
13364         } else {
13365             *(*dest)++ = *src++;
13366         }
13367     }
13368 }
13369
13370 char *
13371 DefaultFileName (char *ext)
13372 {
13373     static char def[MSG_SIZ];
13374     char *p;
13375
13376     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13377         p = def;
13378         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13379         *p++ = '-';
13380         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13381         *p++ = '.';
13382         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13383     } else {
13384         def[0] = NULLCHAR;
13385     }
13386     return def;
13387 }
13388
13389 /* Save the current game to the given file */
13390 int
13391 SaveGameToFile (char *filename, int append)
13392 {
13393     FILE *f;
13394     char buf[MSG_SIZ];
13395     int result, i, t,tot=0;
13396
13397     if (strcmp(filename, "-") == 0) {
13398         return SaveGame(stdout, 0, NULL);
13399     } else {
13400         for(i=0; i<10; i++) { // upto 10 tries
13401              f = fopen(filename, append ? "a" : "w");
13402              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13403              if(f || errno != 13) break;
13404              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13405              tot += t;
13406         }
13407         if (f == NULL) {
13408             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13409             DisplayError(buf, errno);
13410             return FALSE;
13411         } else {
13412             safeStrCpy(buf, lastMsg, MSG_SIZ);
13413             DisplayMessage(_("Waiting for access to save file"), "");
13414             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13415             DisplayMessage(_("Saving game"), "");
13416             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13417             result = SaveGame(f, 0, NULL);
13418             DisplayMessage(buf, "");
13419             return result;
13420         }
13421     }
13422 }
13423
13424 char *
13425 SavePart (char *str)
13426 {
13427     static char buf[MSG_SIZ];
13428     char *p;
13429
13430     p = strchr(str, ' ');
13431     if (p == NULL) return str;
13432     strncpy(buf, str, p - str);
13433     buf[p - str] = NULLCHAR;
13434     return buf;
13435 }
13436
13437 #define PGN_MAX_LINE 75
13438
13439 #define PGN_SIDE_WHITE  0
13440 #define PGN_SIDE_BLACK  1
13441
13442 static int
13443 FindFirstMoveOutOfBook (int side)
13444 {
13445     int result = -1;
13446
13447     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13448         int index = backwardMostMove;
13449         int has_book_hit = 0;
13450
13451         if( (index % 2) != side ) {
13452             index++;
13453         }
13454
13455         while( index < forwardMostMove ) {
13456             /* Check to see if engine is in book */
13457             int depth = pvInfoList[index].depth;
13458             int score = pvInfoList[index].score;
13459             int in_book = 0;
13460
13461             if( depth <= 2 ) {
13462                 in_book = 1;
13463             }
13464             else if( score == 0 && depth == 63 ) {
13465                 in_book = 1; /* Zappa */
13466             }
13467             else if( score == 2 && depth == 99 ) {
13468                 in_book = 1; /* Abrok */
13469             }
13470
13471             has_book_hit += in_book;
13472
13473             if( ! in_book ) {
13474                 result = index;
13475
13476                 break;
13477             }
13478
13479             index += 2;
13480         }
13481     }
13482
13483     return result;
13484 }
13485
13486 void
13487 GetOutOfBookInfo (char * buf)
13488 {
13489     int oob[2];
13490     int i;
13491     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13492
13493     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13494     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13495
13496     *buf = '\0';
13497
13498     if( oob[0] >= 0 || oob[1] >= 0 ) {
13499         for( i=0; i<2; i++ ) {
13500             int idx = oob[i];
13501
13502             if( idx >= 0 ) {
13503                 if( i > 0 && oob[0] >= 0 ) {
13504                     strcat( buf, "   " );
13505                 }
13506
13507                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13508                 sprintf( buf+strlen(buf), "%s%.2f",
13509                     pvInfoList[idx].score >= 0 ? "+" : "",
13510                     pvInfoList[idx].score / 100.0 );
13511             }
13512         }
13513     }
13514 }
13515
13516 /* Save game in PGN style */
13517 static void
13518 SaveGamePGN2 (FILE *f)
13519 {
13520     int i, offset, linelen, newblock;
13521 //    char *movetext;
13522     char numtext[32];
13523     int movelen, numlen, blank;
13524     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13525
13526     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13527
13528     PrintPGNTags(f, &gameInfo);
13529
13530     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13531
13532     if (backwardMostMove > 0 || startedFromSetupPosition) {
13533         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13534         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13535         fprintf(f, "\n{--------------\n");
13536         PrintPosition(f, backwardMostMove);
13537         fprintf(f, "--------------}\n");
13538         free(fen);
13539     }
13540     else {
13541         /* [AS] Out of book annotation */
13542         if( appData.saveOutOfBookInfo ) {
13543             char buf[64];
13544
13545             GetOutOfBookInfo( buf );
13546
13547             if( buf[0] != '\0' ) {
13548                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13549             }
13550         }
13551
13552         fprintf(f, "\n");
13553     }
13554
13555     i = backwardMostMove;
13556     linelen = 0;
13557     newblock = TRUE;
13558
13559     while (i < forwardMostMove) {
13560         /* Print comments preceding this move */
13561         if (commentList[i] != NULL) {
13562             if (linelen > 0) fprintf(f, "\n");
13563             fprintf(f, "%s", commentList[i]);
13564             linelen = 0;
13565             newblock = TRUE;
13566         }
13567
13568         /* Format move number */
13569         if ((i % 2) == 0)
13570           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13571         else
13572           if (newblock)
13573             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13574           else
13575             numtext[0] = NULLCHAR;
13576
13577         numlen = strlen(numtext);
13578         newblock = FALSE;
13579
13580         /* Print move number */
13581         blank = linelen > 0 && numlen > 0;
13582         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13583             fprintf(f, "\n");
13584             linelen = 0;
13585             blank = 0;
13586         }
13587         if (blank) {
13588             fprintf(f, " ");
13589             linelen++;
13590         }
13591         fprintf(f, "%s", numtext);
13592         linelen += numlen;
13593
13594         /* Get move */
13595         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13596         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13597
13598         /* Print move */
13599         blank = linelen > 0 && movelen > 0;
13600         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13601             fprintf(f, "\n");
13602             linelen = 0;
13603             blank = 0;
13604         }
13605         if (blank) {
13606             fprintf(f, " ");
13607             linelen++;
13608         }
13609         fprintf(f, "%s", move_buffer);
13610         linelen += movelen;
13611
13612         /* [AS] Add PV info if present */
13613         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13614             /* [HGM] add time */
13615             char buf[MSG_SIZ]; int seconds;
13616
13617             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13618
13619             if( seconds <= 0)
13620               buf[0] = 0;
13621             else
13622               if( seconds < 30 )
13623                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13624               else
13625                 {
13626                   seconds = (seconds + 4)/10; // round to full seconds
13627                   if( seconds < 60 )
13628                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13629                   else
13630                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13631                 }
13632
13633             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13634                       pvInfoList[i].score >= 0 ? "+" : "",
13635                       pvInfoList[i].score / 100.0,
13636                       pvInfoList[i].depth,
13637                       buf );
13638
13639             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13640
13641             /* Print score/depth */
13642             blank = linelen > 0 && movelen > 0;
13643             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13644                 fprintf(f, "\n");
13645                 linelen = 0;
13646                 blank = 0;
13647             }
13648             if (blank) {
13649                 fprintf(f, " ");
13650                 linelen++;
13651             }
13652             fprintf(f, "%s", move_buffer);
13653             linelen += movelen;
13654         }
13655
13656         i++;
13657     }
13658
13659     /* Start a new line */
13660     if (linelen > 0) fprintf(f, "\n");
13661
13662     /* Print comments after last move */
13663     if (commentList[i] != NULL) {
13664         fprintf(f, "%s\n", commentList[i]);
13665     }
13666
13667     /* Print result */
13668     if (gameInfo.resultDetails != NULL &&
13669         gameInfo.resultDetails[0] != NULLCHAR) {
13670         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13671         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13672            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13673             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13674         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13675     } else {
13676         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13677     }
13678 }
13679
13680 /* Save game in PGN style and close the file */
13681 int
13682 SaveGamePGN (FILE *f)
13683 {
13684     SaveGamePGN2(f);
13685     fclose(f);
13686     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13687     return TRUE;
13688 }
13689
13690 /* Save game in old style and close the file */
13691 int
13692 SaveGameOldStyle (FILE *f)
13693 {
13694     int i, offset;
13695     time_t tm;
13696
13697     tm = time((time_t *) NULL);
13698
13699     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13700     PrintOpponents(f);
13701
13702     if (backwardMostMove > 0 || startedFromSetupPosition) {
13703         fprintf(f, "\n[--------------\n");
13704         PrintPosition(f, backwardMostMove);
13705         fprintf(f, "--------------]\n");
13706     } else {
13707         fprintf(f, "\n");
13708     }
13709
13710     i = backwardMostMove;
13711     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13712
13713     while (i < forwardMostMove) {
13714         if (commentList[i] != NULL) {
13715             fprintf(f, "[%s]\n", commentList[i]);
13716         }
13717
13718         if ((i % 2) == 1) {
13719             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13720             i++;
13721         } else {
13722             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13723             i++;
13724             if (commentList[i] != NULL) {
13725                 fprintf(f, "\n");
13726                 continue;
13727             }
13728             if (i >= forwardMostMove) {
13729                 fprintf(f, "\n");
13730                 break;
13731             }
13732             fprintf(f, "%s\n", parseList[i]);
13733             i++;
13734         }
13735     }
13736
13737     if (commentList[i] != NULL) {
13738         fprintf(f, "[%s]\n", commentList[i]);
13739     }
13740
13741     /* This isn't really the old style, but it's close enough */
13742     if (gameInfo.resultDetails != NULL &&
13743         gameInfo.resultDetails[0] != NULLCHAR) {
13744         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13745                 gameInfo.resultDetails);
13746     } else {
13747         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13748     }
13749
13750     fclose(f);
13751     return TRUE;
13752 }
13753
13754 /* Save the current game to open file f and close the file */
13755 int
13756 SaveGame (FILE *f, int dummy, char *dummy2)
13757 {
13758     if (gameMode == EditPosition) EditPositionDone(TRUE);
13759     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13760     if (appData.oldSaveStyle)
13761       return SaveGameOldStyle(f);
13762     else
13763       return SaveGamePGN(f);
13764 }
13765
13766 /* Save the current position to the given file */
13767 int
13768 SavePositionToFile (char *filename)
13769 {
13770     FILE *f;
13771     char buf[MSG_SIZ];
13772
13773     if (strcmp(filename, "-") == 0) {
13774         return SavePosition(stdout, 0, NULL);
13775     } else {
13776         f = fopen(filename, "a");
13777         if (f == NULL) {
13778             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13779             DisplayError(buf, errno);
13780             return FALSE;
13781         } else {
13782             safeStrCpy(buf, lastMsg, MSG_SIZ);
13783             DisplayMessage(_("Waiting for access to save file"), "");
13784             flock(fileno(f), LOCK_EX); // [HGM] lock
13785             DisplayMessage(_("Saving position"), "");
13786             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13787             SavePosition(f, 0, NULL);
13788             DisplayMessage(buf, "");
13789             return TRUE;
13790         }
13791     }
13792 }
13793
13794 /* Save the current position to the given open file and close the file */
13795 int
13796 SavePosition (FILE *f, int dummy, char *dummy2)
13797 {
13798     time_t tm;
13799     char *fen;
13800
13801     if (gameMode == EditPosition) EditPositionDone(TRUE);
13802     if (appData.oldSaveStyle) {
13803         tm = time((time_t *) NULL);
13804
13805         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13806         PrintOpponents(f);
13807         fprintf(f, "[--------------\n");
13808         PrintPosition(f, currentMove);
13809         fprintf(f, "--------------]\n");
13810     } else {
13811         fen = PositionToFEN(currentMove, NULL, 1);
13812         fprintf(f, "%s\n", fen);
13813         free(fen);
13814     }
13815     fclose(f);
13816     return TRUE;
13817 }
13818
13819 void
13820 ReloadCmailMsgEvent (int unregister)
13821 {
13822 #if !WIN32
13823     static char *inFilename = NULL;
13824     static char *outFilename;
13825     int i;
13826     struct stat inbuf, outbuf;
13827     int status;
13828
13829     /* Any registered moves are unregistered if unregister is set, */
13830     /* i.e. invoked by the signal handler */
13831     if (unregister) {
13832         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13833             cmailMoveRegistered[i] = FALSE;
13834             if (cmailCommentList[i] != NULL) {
13835                 free(cmailCommentList[i]);
13836                 cmailCommentList[i] = NULL;
13837             }
13838         }
13839         nCmailMovesRegistered = 0;
13840     }
13841
13842     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13843         cmailResult[i] = CMAIL_NOT_RESULT;
13844     }
13845     nCmailResults = 0;
13846
13847     if (inFilename == NULL) {
13848         /* Because the filenames are static they only get malloced once  */
13849         /* and they never get freed                                      */
13850         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13851         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13852
13853         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13854         sprintf(outFilename, "%s.out", appData.cmailGameName);
13855     }
13856
13857     status = stat(outFilename, &outbuf);
13858     if (status < 0) {
13859         cmailMailedMove = FALSE;
13860     } else {
13861         status = stat(inFilename, &inbuf);
13862         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13863     }
13864
13865     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13866        counts the games, notes how each one terminated, etc.
13867
13868        It would be nice to remove this kludge and instead gather all
13869        the information while building the game list.  (And to keep it
13870        in the game list nodes instead of having a bunch of fixed-size
13871        parallel arrays.)  Note this will require getting each game's
13872        termination from the PGN tags, as the game list builder does
13873        not process the game moves.  --mann
13874        */
13875     cmailMsgLoaded = TRUE;
13876     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13877
13878     /* Load first game in the file or popup game menu */
13879     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13880
13881 #endif /* !WIN32 */
13882     return;
13883 }
13884
13885 int
13886 RegisterMove ()
13887 {
13888     FILE *f;
13889     char string[MSG_SIZ];
13890
13891     if (   cmailMailedMove
13892         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13893         return TRUE;            /* Allow free viewing  */
13894     }
13895
13896     /* Unregister move to ensure that we don't leave RegisterMove        */
13897     /* with the move registered when the conditions for registering no   */
13898     /* longer hold                                                       */
13899     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13900         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13901         nCmailMovesRegistered --;
13902
13903         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13904           {
13905               free(cmailCommentList[lastLoadGameNumber - 1]);
13906               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13907           }
13908     }
13909
13910     if (cmailOldMove == -1) {
13911         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13912         return FALSE;
13913     }
13914
13915     if (currentMove > cmailOldMove + 1) {
13916         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13917         return FALSE;
13918     }
13919
13920     if (currentMove < cmailOldMove) {
13921         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13922         return FALSE;
13923     }
13924
13925     if (forwardMostMove > currentMove) {
13926         /* Silently truncate extra moves */
13927         TruncateGame();
13928     }
13929
13930     if (   (currentMove == cmailOldMove + 1)
13931         || (   (currentMove == cmailOldMove)
13932             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13933                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13934         if (gameInfo.result != GameUnfinished) {
13935             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13936         }
13937
13938         if (commentList[currentMove] != NULL) {
13939             cmailCommentList[lastLoadGameNumber - 1]
13940               = StrSave(commentList[currentMove]);
13941         }
13942         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13943
13944         if (appData.debugMode)
13945           fprintf(debugFP, "Saving %s for game %d\n",
13946                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13947
13948         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13949
13950         f = fopen(string, "w");
13951         if (appData.oldSaveStyle) {
13952             SaveGameOldStyle(f); /* also closes the file */
13953
13954             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13955             f = fopen(string, "w");
13956             SavePosition(f, 0, NULL); /* also closes the file */
13957         } else {
13958             fprintf(f, "{--------------\n");
13959             PrintPosition(f, currentMove);
13960             fprintf(f, "--------------}\n\n");
13961
13962             SaveGame(f, 0, NULL); /* also closes the file*/
13963         }
13964
13965         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13966         nCmailMovesRegistered ++;
13967     } else if (nCmailGames == 1) {
13968         DisplayError(_("You have not made a move yet"), 0);
13969         return FALSE;
13970     }
13971
13972     return TRUE;
13973 }
13974
13975 void
13976 MailMoveEvent ()
13977 {
13978 #if !WIN32
13979     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13980     FILE *commandOutput;
13981     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13982     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13983     int nBuffers;
13984     int i;
13985     int archived;
13986     char *arcDir;
13987
13988     if (! cmailMsgLoaded) {
13989         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13990         return;
13991     }
13992
13993     if (nCmailGames == nCmailResults) {
13994         DisplayError(_("No unfinished games"), 0);
13995         return;
13996     }
13997
13998 #if CMAIL_PROHIBIT_REMAIL
13999     if (cmailMailedMove) {
14000       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);
14001         DisplayError(msg, 0);
14002         return;
14003     }
14004 #endif
14005
14006     if (! (cmailMailedMove || RegisterMove())) return;
14007
14008     if (   cmailMailedMove
14009         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14010       snprintf(string, MSG_SIZ, partCommandString,
14011                appData.debugMode ? " -v" : "", appData.cmailGameName);
14012         commandOutput = popen(string, "r");
14013
14014         if (commandOutput == NULL) {
14015             DisplayError(_("Failed to invoke cmail"), 0);
14016         } else {
14017             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14018                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14019             }
14020             if (nBuffers > 1) {
14021                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14022                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14023                 nBytes = MSG_SIZ - 1;
14024             } else {
14025                 (void) memcpy(msg, buffer, nBytes);
14026             }
14027             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14028
14029             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14030                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14031
14032                 archived = TRUE;
14033                 for (i = 0; i < nCmailGames; i ++) {
14034                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14035                         archived = FALSE;
14036                     }
14037                 }
14038                 if (   archived
14039                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14040                         != NULL)) {
14041                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14042                            arcDir,
14043                            appData.cmailGameName,
14044                            gameInfo.date);
14045                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14046                     cmailMsgLoaded = FALSE;
14047                 }
14048             }
14049
14050             DisplayInformation(msg);
14051             pclose(commandOutput);
14052         }
14053     } else {
14054         if ((*cmailMsg) != '\0') {
14055             DisplayInformation(cmailMsg);
14056         }
14057     }
14058
14059     return;
14060 #endif /* !WIN32 */
14061 }
14062
14063 char *
14064 CmailMsg ()
14065 {
14066 #if WIN32
14067     return NULL;
14068 #else
14069     int  prependComma = 0;
14070     char number[5];
14071     char string[MSG_SIZ];       /* Space for game-list */
14072     int  i;
14073
14074     if (!cmailMsgLoaded) return "";
14075
14076     if (cmailMailedMove) {
14077       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14078     } else {
14079         /* Create a list of games left */
14080       snprintf(string, MSG_SIZ, "[");
14081         for (i = 0; i < nCmailGames; i ++) {
14082             if (! (   cmailMoveRegistered[i]
14083                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14084                 if (prependComma) {
14085                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14086                 } else {
14087                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14088                     prependComma = 1;
14089                 }
14090
14091                 strcat(string, number);
14092             }
14093         }
14094         strcat(string, "]");
14095
14096         if (nCmailMovesRegistered + nCmailResults == 0) {
14097             switch (nCmailGames) {
14098               case 1:
14099                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14100                 break;
14101
14102               case 2:
14103                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14104                 break;
14105
14106               default:
14107                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14108                          nCmailGames);
14109                 break;
14110             }
14111         } else {
14112             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14113               case 1:
14114                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14115                          string);
14116                 break;
14117
14118               case 0:
14119                 if (nCmailResults == nCmailGames) {
14120                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14121                 } else {
14122                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14123                 }
14124                 break;
14125
14126               default:
14127                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14128                          string);
14129             }
14130         }
14131     }
14132     return cmailMsg;
14133 #endif /* WIN32 */
14134 }
14135
14136 void
14137 ResetGameEvent ()
14138 {
14139     if (gameMode == Training)
14140       SetTrainingModeOff();
14141
14142     Reset(TRUE, TRUE);
14143     cmailMsgLoaded = FALSE;
14144     if (appData.icsActive) {
14145       SendToICS(ics_prefix);
14146       SendToICS("refresh\n");
14147     }
14148 }
14149
14150 void
14151 ExitEvent (int status)
14152 {
14153     exiting++;
14154     if (exiting > 2) {
14155       /* Give up on clean exit */
14156       exit(status);
14157     }
14158     if (exiting > 1) {
14159       /* Keep trying for clean exit */
14160       return;
14161     }
14162
14163     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14164     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14165
14166     if (telnetISR != NULL) {
14167       RemoveInputSource(telnetISR);
14168     }
14169     if (icsPR != NoProc) {
14170       DestroyChildProcess(icsPR, TRUE);
14171     }
14172
14173     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14174     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14175
14176     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14177     /* make sure this other one finishes before killing it!                  */
14178     if(endingGame) { int count = 0;
14179         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14180         while(endingGame && count++ < 10) DoSleep(1);
14181         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14182     }
14183
14184     /* Kill off chess programs */
14185     if (first.pr != NoProc) {
14186         ExitAnalyzeMode();
14187
14188         DoSleep( appData.delayBeforeQuit );
14189         SendToProgram("quit\n", &first);
14190         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14191     }
14192     if (second.pr != NoProc) {
14193         DoSleep( appData.delayBeforeQuit );
14194         SendToProgram("quit\n", &second);
14195         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14196     }
14197     if (first.isr != NULL) {
14198         RemoveInputSource(first.isr);
14199     }
14200     if (second.isr != NULL) {
14201         RemoveInputSource(second.isr);
14202     }
14203
14204     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14205     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14206
14207     ShutDownFrontEnd();
14208     exit(status);
14209 }
14210
14211 void
14212 PauseEngine (ChessProgramState *cps)
14213 {
14214     SendToProgram("pause\n", cps);
14215     cps->pause = 2;
14216 }
14217
14218 void
14219 UnPauseEngine (ChessProgramState *cps)
14220 {
14221     SendToProgram("resume\n", cps);
14222     cps->pause = 1;
14223 }
14224
14225 void
14226 PauseEvent ()
14227 {
14228     if (appData.debugMode)
14229         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14230     if (pausing) {
14231         pausing = FALSE;
14232         ModeHighlight();
14233         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14234             StartClocks();
14235             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14236                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14237                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14238             }
14239             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14240             HandleMachineMove(stashedInputMove, stalledEngine);
14241             stalledEngine = NULL;
14242             return;
14243         }
14244         if (gameMode == MachinePlaysWhite ||
14245             gameMode == TwoMachinesPlay   ||
14246             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14247             if(first.pause)  UnPauseEngine(&first);
14248             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14249             if(second.pause) UnPauseEngine(&second);
14250             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14251             StartClocks();
14252         } else {
14253             DisplayBothClocks();
14254         }
14255         if (gameMode == PlayFromGameFile) {
14256             if (appData.timeDelay >= 0)
14257                 AutoPlayGameLoop();
14258         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14259             Reset(FALSE, TRUE);
14260             SendToICS(ics_prefix);
14261             SendToICS("refresh\n");
14262         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14263             ForwardInner(forwardMostMove);
14264         }
14265         pauseExamInvalid = FALSE;
14266     } else {
14267         switch (gameMode) {
14268           default:
14269             return;
14270           case IcsExamining:
14271             pauseExamForwardMostMove = forwardMostMove;
14272             pauseExamInvalid = FALSE;
14273             /* fall through */
14274           case IcsObserving:
14275           case IcsPlayingWhite:
14276           case IcsPlayingBlack:
14277             pausing = TRUE;
14278             ModeHighlight();
14279             return;
14280           case PlayFromGameFile:
14281             (void) StopLoadGameTimer();
14282             pausing = TRUE;
14283             ModeHighlight();
14284             break;
14285           case BeginningOfGame:
14286             if (appData.icsActive) return;
14287             /* else fall through */
14288           case MachinePlaysWhite:
14289           case MachinePlaysBlack:
14290           case TwoMachinesPlay:
14291             if (forwardMostMove == 0)
14292               return;           /* don't pause if no one has moved */
14293             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14294                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14295                 if(onMove->pause) {           // thinking engine can be paused
14296                     PauseEngine(onMove);      // do it
14297                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14298                         PauseEngine(onMove->other);
14299                     else
14300                         SendToProgram("easy\n", onMove->other);
14301                     StopClocks();
14302                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14303             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14304                 if(first.pause) {
14305                     PauseEngine(&first);
14306                     StopClocks();
14307                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14308             } else { // human on move, pause pondering by either method
14309                 if(first.pause)
14310                     PauseEngine(&first);
14311                 else if(appData.ponderNextMove)
14312                     SendToProgram("easy\n", &first);
14313                 StopClocks();
14314             }
14315             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14316           case AnalyzeMode:
14317             pausing = TRUE;
14318             ModeHighlight();
14319             break;
14320         }
14321     }
14322 }
14323
14324 void
14325 EditCommentEvent ()
14326 {
14327     char title[MSG_SIZ];
14328
14329     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14330       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14331     } else {
14332       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14333                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14334                parseList[currentMove - 1]);
14335     }
14336
14337     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14338 }
14339
14340
14341 void
14342 EditTagsEvent ()
14343 {
14344     char *tags = PGNTags(&gameInfo);
14345     bookUp = FALSE;
14346     EditTagsPopUp(tags, NULL);
14347     free(tags);
14348 }
14349
14350 void
14351 ToggleSecond ()
14352 {
14353   if(second.analyzing) {
14354     SendToProgram("exit\n", &second);
14355     second.analyzing = FALSE;
14356   } else {
14357     if (second.pr == NoProc) StartChessProgram(&second);
14358     InitChessProgram(&second, FALSE);
14359     FeedMovesToProgram(&second, currentMove);
14360
14361     SendToProgram("analyze\n", &second);
14362     second.analyzing = TRUE;
14363   }
14364 }
14365
14366 /* Toggle ShowThinking */
14367 void
14368 ToggleShowThinking()
14369 {
14370   appData.showThinking = !appData.showThinking;
14371   ShowThinkingEvent();
14372 }
14373
14374 int
14375 AnalyzeModeEvent ()
14376 {
14377     char buf[MSG_SIZ];
14378
14379     if (!first.analysisSupport) {
14380       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14381       DisplayError(buf, 0);
14382       return 0;
14383     }
14384     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14385     if (appData.icsActive) {
14386         if (gameMode != IcsObserving) {
14387           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14388             DisplayError(buf, 0);
14389             /* secure check */
14390             if (appData.icsEngineAnalyze) {
14391                 if (appData.debugMode)
14392                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14393                 ExitAnalyzeMode();
14394                 ModeHighlight();
14395             }
14396             return 0;
14397         }
14398         /* if enable, user wants to disable icsEngineAnalyze */
14399         if (appData.icsEngineAnalyze) {
14400                 ExitAnalyzeMode();
14401                 ModeHighlight();
14402                 return 0;
14403         }
14404         appData.icsEngineAnalyze = TRUE;
14405         if (appData.debugMode)
14406             fprintf(debugFP, "ICS engine analyze starting... \n");
14407     }
14408
14409     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14410     if (appData.noChessProgram || gameMode == AnalyzeMode)
14411       return 0;
14412
14413     if (gameMode != AnalyzeFile) {
14414         if (!appData.icsEngineAnalyze) {
14415                EditGameEvent();
14416                if (gameMode != EditGame) return 0;
14417         }
14418         if (!appData.showThinking) ToggleShowThinking();
14419         ResurrectChessProgram();
14420         SendToProgram("analyze\n", &first);
14421         first.analyzing = TRUE;
14422         /*first.maybeThinking = TRUE;*/
14423         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14424         EngineOutputPopUp();
14425     }
14426     if (!appData.icsEngineAnalyze) {
14427         gameMode = AnalyzeMode;
14428         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14429     }
14430     pausing = FALSE;
14431     ModeHighlight();
14432     SetGameInfo();
14433
14434     StartAnalysisClock();
14435     GetTimeMark(&lastNodeCountTime);
14436     lastNodeCount = 0;
14437     return 1;
14438 }
14439
14440 void
14441 AnalyzeFileEvent ()
14442 {
14443     if (appData.noChessProgram || gameMode == AnalyzeFile)
14444       return;
14445
14446     if (!first.analysisSupport) {
14447       char buf[MSG_SIZ];
14448       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14449       DisplayError(buf, 0);
14450       return;
14451     }
14452
14453     if (gameMode != AnalyzeMode) {
14454         keepInfo = 1; // mere annotating should not alter PGN tags
14455         EditGameEvent();
14456         keepInfo = 0;
14457         if (gameMode != EditGame) return;
14458         if (!appData.showThinking) ToggleShowThinking();
14459         ResurrectChessProgram();
14460         SendToProgram("analyze\n", &first);
14461         first.analyzing = TRUE;
14462         /*first.maybeThinking = TRUE;*/
14463         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14464         EngineOutputPopUp();
14465     }
14466     gameMode = AnalyzeFile;
14467     pausing = FALSE;
14468     ModeHighlight();
14469
14470     StartAnalysisClock();
14471     GetTimeMark(&lastNodeCountTime);
14472     lastNodeCount = 0;
14473     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14474     AnalysisPeriodicEvent(1);
14475 }
14476
14477 void
14478 MachineWhiteEvent ()
14479 {
14480     char buf[MSG_SIZ];
14481     char *bookHit = NULL;
14482
14483     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14484       return;
14485
14486
14487     if (gameMode == PlayFromGameFile ||
14488         gameMode == TwoMachinesPlay  ||
14489         gameMode == Training         ||
14490         gameMode == AnalyzeMode      ||
14491         gameMode == EndOfGame)
14492         EditGameEvent();
14493
14494     if (gameMode == EditPosition)
14495         EditPositionDone(TRUE);
14496
14497     if (!WhiteOnMove(currentMove)) {
14498         DisplayError(_("It is not White's turn"), 0);
14499         return;
14500     }
14501
14502     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14503       ExitAnalyzeMode();
14504
14505     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14506         gameMode == AnalyzeFile)
14507         TruncateGame();
14508
14509     ResurrectChessProgram();    /* in case it isn't running */
14510     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14511         gameMode = MachinePlaysWhite;
14512         ResetClocks();
14513     } else
14514     gameMode = MachinePlaysWhite;
14515     pausing = FALSE;
14516     ModeHighlight();
14517     SetGameInfo();
14518     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14519     DisplayTitle(buf);
14520     if (first.sendName) {
14521       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14522       SendToProgram(buf, &first);
14523     }
14524     if (first.sendTime) {
14525       if (first.useColors) {
14526         SendToProgram("black\n", &first); /*gnu kludge*/
14527       }
14528       SendTimeRemaining(&first, TRUE);
14529     }
14530     if (first.useColors) {
14531       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14532     }
14533     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14534     SetMachineThinkingEnables();
14535     first.maybeThinking = TRUE;
14536     StartClocks();
14537     firstMove = FALSE;
14538
14539     if (appData.autoFlipView && !flipView) {
14540       flipView = !flipView;
14541       DrawPosition(FALSE, NULL);
14542       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14543     }
14544
14545     if(bookHit) { // [HGM] book: simulate book reply
14546         static char bookMove[MSG_SIZ]; // a bit generous?
14547
14548         programStats.nodes = programStats.depth = programStats.time =
14549         programStats.score = programStats.got_only_move = 0;
14550         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14551
14552         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14553         strcat(bookMove, bookHit);
14554         HandleMachineMove(bookMove, &first);
14555     }
14556 }
14557
14558 void
14559 MachineBlackEvent ()
14560 {
14561   char buf[MSG_SIZ];
14562   char *bookHit = NULL;
14563
14564     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14565         return;
14566
14567
14568     if (gameMode == PlayFromGameFile ||
14569         gameMode == TwoMachinesPlay  ||
14570         gameMode == Training         ||
14571         gameMode == AnalyzeMode      ||
14572         gameMode == EndOfGame)
14573         EditGameEvent();
14574
14575     if (gameMode == EditPosition)
14576         EditPositionDone(TRUE);
14577
14578     if (WhiteOnMove(currentMove)) {
14579         DisplayError(_("It is not Black's turn"), 0);
14580         return;
14581     }
14582
14583     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14584       ExitAnalyzeMode();
14585
14586     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14587         gameMode == AnalyzeFile)
14588         TruncateGame();
14589
14590     ResurrectChessProgram();    /* in case it isn't running */
14591     gameMode = MachinePlaysBlack;
14592     pausing = FALSE;
14593     ModeHighlight();
14594     SetGameInfo();
14595     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14596     DisplayTitle(buf);
14597     if (first.sendName) {
14598       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14599       SendToProgram(buf, &first);
14600     }
14601     if (first.sendTime) {
14602       if (first.useColors) {
14603         SendToProgram("white\n", &first); /*gnu kludge*/
14604       }
14605       SendTimeRemaining(&first, FALSE);
14606     }
14607     if (first.useColors) {
14608       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14609     }
14610     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14611     SetMachineThinkingEnables();
14612     first.maybeThinking = TRUE;
14613     StartClocks();
14614
14615     if (appData.autoFlipView && flipView) {
14616       flipView = !flipView;
14617       DrawPosition(FALSE, NULL);
14618       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14619     }
14620     if(bookHit) { // [HGM] book: simulate book reply
14621         static char bookMove[MSG_SIZ]; // a bit generous?
14622
14623         programStats.nodes = programStats.depth = programStats.time =
14624         programStats.score = programStats.got_only_move = 0;
14625         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14626
14627         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14628         strcat(bookMove, bookHit);
14629         HandleMachineMove(bookMove, &first);
14630     }
14631 }
14632
14633
14634 void
14635 DisplayTwoMachinesTitle ()
14636 {
14637     char buf[MSG_SIZ];
14638     if (appData.matchGames > 0) {
14639         if(appData.tourneyFile[0]) {
14640           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14641                    gameInfo.white, _("vs."), gameInfo.black,
14642                    nextGame+1, appData.matchGames+1,
14643                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14644         } else
14645         if (first.twoMachinesColor[0] == 'w') {
14646           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14647                    gameInfo.white, _("vs."),  gameInfo.black,
14648                    first.matchWins, second.matchWins,
14649                    matchGame - 1 - (first.matchWins + second.matchWins));
14650         } else {
14651           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14652                    gameInfo.white, _("vs."), gameInfo.black,
14653                    second.matchWins, first.matchWins,
14654                    matchGame - 1 - (first.matchWins + second.matchWins));
14655         }
14656     } else {
14657       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14658     }
14659     DisplayTitle(buf);
14660 }
14661
14662 void
14663 SettingsMenuIfReady ()
14664 {
14665   if (second.lastPing != second.lastPong) {
14666     DisplayMessage("", _("Waiting for second chess program"));
14667     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14668     return;
14669   }
14670   ThawUI();
14671   DisplayMessage("", "");
14672   SettingsPopUp(&second);
14673 }
14674
14675 int
14676 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14677 {
14678     char buf[MSG_SIZ];
14679     if (cps->pr == NoProc) {
14680         StartChessProgram(cps);
14681         if (cps->protocolVersion == 1) {
14682           retry();
14683           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14684         } else {
14685           /* kludge: allow timeout for initial "feature" command */
14686           if(retry != TwoMachinesEventIfReady) FreezeUI();
14687           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14688           DisplayMessage("", buf);
14689           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14690         }
14691         return 1;
14692     }
14693     return 0;
14694 }
14695
14696 void
14697 TwoMachinesEvent P((void))
14698 {
14699     int i;
14700     char buf[MSG_SIZ];
14701     ChessProgramState *onmove;
14702     char *bookHit = NULL;
14703     static int stalling = 0;
14704     TimeMark now;
14705     long wait;
14706
14707     if (appData.noChessProgram) return;
14708
14709     switch (gameMode) {
14710       case TwoMachinesPlay:
14711         return;
14712       case MachinePlaysWhite:
14713       case MachinePlaysBlack:
14714         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14715             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14716             return;
14717         }
14718         /* fall through */
14719       case BeginningOfGame:
14720       case PlayFromGameFile:
14721       case EndOfGame:
14722         EditGameEvent();
14723         if (gameMode != EditGame) return;
14724         break;
14725       case EditPosition:
14726         EditPositionDone(TRUE);
14727         break;
14728       case AnalyzeMode:
14729       case AnalyzeFile:
14730         ExitAnalyzeMode();
14731         break;
14732       case EditGame:
14733       default:
14734         break;
14735     }
14736
14737 //    forwardMostMove = currentMove;
14738     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14739     startingEngine = TRUE;
14740
14741     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14742
14743     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14744     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14745       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14746       return;
14747     }
14748     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14749
14750     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14751                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14752         startingEngine = matchMode = FALSE;
14753         DisplayError("second engine does not play this", 0);
14754         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14755         EditGameEvent(); // switch back to EditGame mode
14756         return;
14757     }
14758
14759     if(!stalling) {
14760       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14761       SendToProgram("force\n", &second);
14762       stalling = 1;
14763       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14764       return;
14765     }
14766     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14767     if(appData.matchPause>10000 || appData.matchPause<10)
14768                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14769     wait = SubtractTimeMarks(&now, &pauseStart);
14770     if(wait < appData.matchPause) {
14771         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14772         return;
14773     }
14774     // we are now committed to starting the game
14775     stalling = 0;
14776     DisplayMessage("", "");
14777     if (startedFromSetupPosition) {
14778         SendBoard(&second, backwardMostMove);
14779     if (appData.debugMode) {
14780         fprintf(debugFP, "Two Machines\n");
14781     }
14782     }
14783     for (i = backwardMostMove; i < forwardMostMove; i++) {
14784         SendMoveToProgram(i, &second);
14785     }
14786
14787     gameMode = TwoMachinesPlay;
14788     pausing = startingEngine = FALSE;
14789     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14790     SetGameInfo();
14791     DisplayTwoMachinesTitle();
14792     firstMove = TRUE;
14793     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14794         onmove = &first;
14795     } else {
14796         onmove = &second;
14797     }
14798     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14799     SendToProgram(first.computerString, &first);
14800     if (first.sendName) {
14801       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14802       SendToProgram(buf, &first);
14803     }
14804     SendToProgram(second.computerString, &second);
14805     if (second.sendName) {
14806       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14807       SendToProgram(buf, &second);
14808     }
14809
14810     ResetClocks();
14811     if (!first.sendTime || !second.sendTime) {
14812         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14813         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14814     }
14815     if (onmove->sendTime) {
14816       if (onmove->useColors) {
14817         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14818       }
14819       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14820     }
14821     if (onmove->useColors) {
14822       SendToProgram(onmove->twoMachinesColor, onmove);
14823     }
14824     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14825 //    SendToProgram("go\n", onmove);
14826     onmove->maybeThinking = TRUE;
14827     SetMachineThinkingEnables();
14828
14829     StartClocks();
14830
14831     if(bookHit) { // [HGM] book: simulate book reply
14832         static char bookMove[MSG_SIZ]; // a bit generous?
14833
14834         programStats.nodes = programStats.depth = programStats.time =
14835         programStats.score = programStats.got_only_move = 0;
14836         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14837
14838         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14839         strcat(bookMove, bookHit);
14840         savedMessage = bookMove; // args for deferred call
14841         savedState = onmove;
14842         ScheduleDelayedEvent(DeferredBookMove, 1);
14843     }
14844 }
14845
14846 void
14847 TrainingEvent ()
14848 {
14849     if (gameMode == Training) {
14850       SetTrainingModeOff();
14851       gameMode = PlayFromGameFile;
14852       DisplayMessage("", _("Training mode off"));
14853     } else {
14854       gameMode = Training;
14855       animateTraining = appData.animate;
14856
14857       /* make sure we are not already at the end of the game */
14858       if (currentMove < forwardMostMove) {
14859         SetTrainingModeOn();
14860         DisplayMessage("", _("Training mode on"));
14861       } else {
14862         gameMode = PlayFromGameFile;
14863         DisplayError(_("Already at end of game"), 0);
14864       }
14865     }
14866     ModeHighlight();
14867 }
14868
14869 void
14870 IcsClientEvent ()
14871 {
14872     if (!appData.icsActive) return;
14873     switch (gameMode) {
14874       case IcsPlayingWhite:
14875       case IcsPlayingBlack:
14876       case IcsObserving:
14877       case IcsIdle:
14878       case BeginningOfGame:
14879       case IcsExamining:
14880         return;
14881
14882       case EditGame:
14883         break;
14884
14885       case EditPosition:
14886         EditPositionDone(TRUE);
14887         break;
14888
14889       case AnalyzeMode:
14890       case AnalyzeFile:
14891         ExitAnalyzeMode();
14892         break;
14893
14894       default:
14895         EditGameEvent();
14896         break;
14897     }
14898
14899     gameMode = IcsIdle;
14900     ModeHighlight();
14901     return;
14902 }
14903
14904 void
14905 EditGameEvent ()
14906 {
14907     int i;
14908
14909     switch (gameMode) {
14910       case Training:
14911         SetTrainingModeOff();
14912         break;
14913       case MachinePlaysWhite:
14914       case MachinePlaysBlack:
14915       case BeginningOfGame:
14916         SendToProgram("force\n", &first);
14917         SetUserThinkingEnables();
14918         break;
14919       case PlayFromGameFile:
14920         (void) StopLoadGameTimer();
14921         if (gameFileFP != NULL) {
14922             gameFileFP = NULL;
14923         }
14924         break;
14925       case EditPosition:
14926         EditPositionDone(TRUE);
14927         break;
14928       case AnalyzeMode:
14929       case AnalyzeFile:
14930         ExitAnalyzeMode();
14931         SendToProgram("force\n", &first);
14932         break;
14933       case TwoMachinesPlay:
14934         GameEnds(EndOfFile, NULL, GE_PLAYER);
14935         ResurrectChessProgram();
14936         SetUserThinkingEnables();
14937         break;
14938       case EndOfGame:
14939         ResurrectChessProgram();
14940         break;
14941       case IcsPlayingBlack:
14942       case IcsPlayingWhite:
14943         DisplayError(_("Warning: You are still playing a game"), 0);
14944         break;
14945       case IcsObserving:
14946         DisplayError(_("Warning: You are still observing a game"), 0);
14947         break;
14948       case IcsExamining:
14949         DisplayError(_("Warning: You are still examining a game"), 0);
14950         break;
14951       case IcsIdle:
14952         break;
14953       case EditGame:
14954       default:
14955         return;
14956     }
14957
14958     pausing = FALSE;
14959     StopClocks();
14960     first.offeredDraw = second.offeredDraw = 0;
14961
14962     if (gameMode == PlayFromGameFile) {
14963         whiteTimeRemaining = timeRemaining[0][currentMove];
14964         blackTimeRemaining = timeRemaining[1][currentMove];
14965         DisplayTitle("");
14966     }
14967
14968     if (gameMode == MachinePlaysWhite ||
14969         gameMode == MachinePlaysBlack ||
14970         gameMode == TwoMachinesPlay ||
14971         gameMode == EndOfGame) {
14972         i = forwardMostMove;
14973         while (i > currentMove) {
14974             SendToProgram("undo\n", &first);
14975             i--;
14976         }
14977         if(!adjustedClock) {
14978         whiteTimeRemaining = timeRemaining[0][currentMove];
14979         blackTimeRemaining = timeRemaining[1][currentMove];
14980         DisplayBothClocks();
14981         }
14982         if (whiteFlag || blackFlag) {
14983             whiteFlag = blackFlag = 0;
14984         }
14985         DisplayTitle("");
14986     }
14987
14988     gameMode = EditGame;
14989     ModeHighlight();
14990     SetGameInfo();
14991 }
14992
14993
14994 void
14995 EditPositionEvent ()
14996 {
14997     if (gameMode == EditPosition) {
14998         EditGameEvent();
14999         return;
15000     }
15001
15002     EditGameEvent();
15003     if (gameMode != EditGame) return;
15004
15005     gameMode = EditPosition;
15006     ModeHighlight();
15007     SetGameInfo();
15008     if (currentMove > 0)
15009       CopyBoard(boards[0], boards[currentMove]);
15010
15011     blackPlaysFirst = !WhiteOnMove(currentMove);
15012     ResetClocks();
15013     currentMove = forwardMostMove = backwardMostMove = 0;
15014     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15015     DisplayMove(-1);
15016     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15017 }
15018
15019 void
15020 ExitAnalyzeMode ()
15021 {
15022     /* [DM] icsEngineAnalyze - possible call from other functions */
15023     if (appData.icsEngineAnalyze) {
15024         appData.icsEngineAnalyze = FALSE;
15025
15026         DisplayMessage("",_("Close ICS engine analyze..."));
15027     }
15028     if (first.analysisSupport && first.analyzing) {
15029       SendToBoth("exit\n");
15030       first.analyzing = second.analyzing = FALSE;
15031     }
15032     thinkOutput[0] = NULLCHAR;
15033 }
15034
15035 void
15036 EditPositionDone (Boolean fakeRights)
15037 {
15038     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15039
15040     startedFromSetupPosition = TRUE;
15041     InitChessProgram(&first, FALSE);
15042     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15043       boards[0][EP_STATUS] = EP_NONE;
15044       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15045       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15046         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15047         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15048       } else boards[0][CASTLING][2] = NoRights;
15049       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15050         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15051         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15052       } else boards[0][CASTLING][5] = NoRights;
15053       if(gameInfo.variant == VariantSChess) {
15054         int i;
15055         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15056           boards[0][VIRGIN][i] = 0;
15057           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15058           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15059         }
15060       }
15061     }
15062     SendToProgram("force\n", &first);
15063     if (blackPlaysFirst) {
15064         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15065         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15066         currentMove = forwardMostMove = backwardMostMove = 1;
15067         CopyBoard(boards[1], boards[0]);
15068     } else {
15069         currentMove = forwardMostMove = backwardMostMove = 0;
15070     }
15071     SendBoard(&first, forwardMostMove);
15072     if (appData.debugMode) {
15073         fprintf(debugFP, "EditPosDone\n");
15074     }
15075     DisplayTitle("");
15076     DisplayMessage("", "");
15077     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15078     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15079     gameMode = EditGame;
15080     ModeHighlight();
15081     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15082     ClearHighlights(); /* [AS] */
15083 }
15084
15085 /* Pause for `ms' milliseconds */
15086 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15087 void
15088 TimeDelay (long ms)
15089 {
15090     TimeMark m1, m2;
15091
15092     GetTimeMark(&m1);
15093     do {
15094         GetTimeMark(&m2);
15095     } while (SubtractTimeMarks(&m2, &m1) < ms);
15096 }
15097
15098 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15099 void
15100 SendMultiLineToICS (char *buf)
15101 {
15102     char temp[MSG_SIZ+1], *p;
15103     int len;
15104
15105     len = strlen(buf);
15106     if (len > MSG_SIZ)
15107       len = MSG_SIZ;
15108
15109     strncpy(temp, buf, len);
15110     temp[len] = 0;
15111
15112     p = temp;
15113     while (*p) {
15114         if (*p == '\n' || *p == '\r')
15115           *p = ' ';
15116         ++p;
15117     }
15118
15119     strcat(temp, "\n");
15120     SendToICS(temp);
15121     SendToPlayer(temp, strlen(temp));
15122 }
15123
15124 void
15125 SetWhiteToPlayEvent ()
15126 {
15127     if (gameMode == EditPosition) {
15128         blackPlaysFirst = FALSE;
15129         DisplayBothClocks();    /* works because currentMove is 0 */
15130     } else if (gameMode == IcsExamining) {
15131         SendToICS(ics_prefix);
15132         SendToICS("tomove white\n");
15133     }
15134 }
15135
15136 void
15137 SetBlackToPlayEvent ()
15138 {
15139     if (gameMode == EditPosition) {
15140         blackPlaysFirst = TRUE;
15141         currentMove = 1;        /* kludge */
15142         DisplayBothClocks();
15143         currentMove = 0;
15144     } else if (gameMode == IcsExamining) {
15145         SendToICS(ics_prefix);
15146         SendToICS("tomove black\n");
15147     }
15148 }
15149
15150 void
15151 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15152 {
15153     char buf[MSG_SIZ];
15154     ChessSquare piece = boards[0][y][x];
15155     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15156     static int lastVariant;
15157
15158     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15159
15160     switch (selection) {
15161       case ClearBoard:
15162         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15163         MarkTargetSquares(1);
15164         CopyBoard(currentBoard, boards[0]);
15165         CopyBoard(menuBoard, initialPosition);
15166         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15167             SendToICS(ics_prefix);
15168             SendToICS("bsetup clear\n");
15169         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15170             SendToICS(ics_prefix);
15171             SendToICS("clearboard\n");
15172         } else {
15173             int nonEmpty = 0;
15174             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15175                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15176                 for (y = 0; y < BOARD_HEIGHT; y++) {
15177                     if (gameMode == IcsExamining) {
15178                         if (boards[currentMove][y][x] != EmptySquare) {
15179                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15180                                     AAA + x, ONE + y);
15181                             SendToICS(buf);
15182                         }
15183                     } else if(boards[0][y][x] != DarkSquare) {
15184                         if(boards[0][y][x] != p) nonEmpty++;
15185                         boards[0][y][x] = p;
15186                     }
15187                 }
15188             }
15189             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15190                 int r;
15191                 for(r = 0; r < BOARD_HEIGHT; r++) {
15192                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15193                     ChessSquare p = menuBoard[r][x];
15194                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15195                   }
15196                 }
15197                 DisplayMessage("Clicking clock again restores position", "");
15198                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15199                 if(!nonEmpty) { // asked to clear an empty board
15200                     CopyBoard(boards[0], menuBoard);
15201                 } else
15202                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15203                     CopyBoard(boards[0], initialPosition);
15204                 } else
15205                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15206                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15207                     CopyBoard(boards[0], erasedBoard);
15208                 } else
15209                     CopyBoard(erasedBoard, currentBoard);
15210
15211             }
15212         }
15213         if (gameMode == EditPosition) {
15214             DrawPosition(FALSE, boards[0]);
15215         }
15216         break;
15217
15218       case WhitePlay:
15219         SetWhiteToPlayEvent();
15220         break;
15221
15222       case BlackPlay:
15223         SetBlackToPlayEvent();
15224         break;
15225
15226       case EmptySquare:
15227         if (gameMode == IcsExamining) {
15228             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15229             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15230             SendToICS(buf);
15231         } else {
15232             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15233                 if(x == BOARD_LEFT-2) {
15234                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15235                     boards[0][y][1] = 0;
15236                 } else
15237                 if(x == BOARD_RGHT+1) {
15238                     if(y >= gameInfo.holdingsSize) break;
15239                     boards[0][y][BOARD_WIDTH-2] = 0;
15240                 } else break;
15241             }
15242             boards[0][y][x] = EmptySquare;
15243             DrawPosition(FALSE, boards[0]);
15244         }
15245         break;
15246
15247       case PromotePiece:
15248         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15249            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15250             selection = (ChessSquare) (PROMOTED piece);
15251         } else if(piece == EmptySquare) selection = WhiteSilver;
15252         else selection = (ChessSquare)((int)piece - 1);
15253         goto defaultlabel;
15254
15255       case DemotePiece:
15256         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15257            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15258             selection = (ChessSquare) (DEMOTED piece);
15259         } else if(piece == EmptySquare) selection = BlackSilver;
15260         else selection = (ChessSquare)((int)piece + 1);
15261         goto defaultlabel;
15262
15263       case WhiteQueen:
15264       case BlackQueen:
15265         if(gameInfo.variant == VariantShatranj ||
15266            gameInfo.variant == VariantXiangqi  ||
15267            gameInfo.variant == VariantCourier  ||
15268            gameInfo.variant == VariantASEAN    ||
15269            gameInfo.variant == VariantMakruk     )
15270             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15271         goto defaultlabel;
15272
15273       case WhiteKing:
15274       case BlackKing:
15275         if(gameInfo.variant == VariantXiangqi)
15276             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15277         if(gameInfo.variant == VariantKnightmate)
15278             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15279       default:
15280         defaultlabel:
15281         if (gameMode == IcsExamining) {
15282             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15283             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15284                      PieceToChar(selection), AAA + x, ONE + y);
15285             SendToICS(buf);
15286         } else {
15287             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15288                 int n;
15289                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15290                     n = PieceToNumber(selection - BlackPawn);
15291                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15292                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15293                     boards[0][BOARD_HEIGHT-1-n][1]++;
15294                 } else
15295                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15296                     n = PieceToNumber(selection);
15297                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15298                     boards[0][n][BOARD_WIDTH-1] = selection;
15299                     boards[0][n][BOARD_WIDTH-2]++;
15300                 }
15301             } else
15302             boards[0][y][x] = selection;
15303             DrawPosition(TRUE, boards[0]);
15304             ClearHighlights();
15305             fromX = fromY = -1;
15306         }
15307         break;
15308     }
15309 }
15310
15311
15312 void
15313 DropMenuEvent (ChessSquare selection, int x, int y)
15314 {
15315     ChessMove moveType;
15316
15317     switch (gameMode) {
15318       case IcsPlayingWhite:
15319       case MachinePlaysBlack:
15320         if (!WhiteOnMove(currentMove)) {
15321             DisplayMoveError(_("It is Black's turn"));
15322             return;
15323         }
15324         moveType = WhiteDrop;
15325         break;
15326       case IcsPlayingBlack:
15327       case MachinePlaysWhite:
15328         if (WhiteOnMove(currentMove)) {
15329             DisplayMoveError(_("It is White's turn"));
15330             return;
15331         }
15332         moveType = BlackDrop;
15333         break;
15334       case EditGame:
15335         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15336         break;
15337       default:
15338         return;
15339     }
15340
15341     if (moveType == BlackDrop && selection < BlackPawn) {
15342       selection = (ChessSquare) ((int) selection
15343                                  + (int) BlackPawn - (int) WhitePawn);
15344     }
15345     if (boards[currentMove][y][x] != EmptySquare) {
15346         DisplayMoveError(_("That square is occupied"));
15347         return;
15348     }
15349
15350     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15351 }
15352
15353 void
15354 AcceptEvent ()
15355 {
15356     /* Accept a pending offer of any kind from opponent */
15357
15358     if (appData.icsActive) {
15359         SendToICS(ics_prefix);
15360         SendToICS("accept\n");
15361     } else if (cmailMsgLoaded) {
15362         if (currentMove == cmailOldMove &&
15363             commentList[cmailOldMove] != NULL &&
15364             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15365                    "Black offers a draw" : "White offers a draw")) {
15366             TruncateGame();
15367             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15368             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15369         } else {
15370             DisplayError(_("There is no pending offer on this move"), 0);
15371             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15372         }
15373     } else {
15374         /* Not used for offers from chess program */
15375     }
15376 }
15377
15378 void
15379 DeclineEvent ()
15380 {
15381     /* Decline a pending offer of any kind from opponent */
15382
15383     if (appData.icsActive) {
15384         SendToICS(ics_prefix);
15385         SendToICS("decline\n");
15386     } else if (cmailMsgLoaded) {
15387         if (currentMove == cmailOldMove &&
15388             commentList[cmailOldMove] != NULL &&
15389             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15390                    "Black offers a draw" : "White offers a draw")) {
15391 #ifdef NOTDEF
15392             AppendComment(cmailOldMove, "Draw declined", TRUE);
15393             DisplayComment(cmailOldMove - 1, "Draw declined");
15394 #endif /*NOTDEF*/
15395         } else {
15396             DisplayError(_("There is no pending offer on this move"), 0);
15397         }
15398     } else {
15399         /* Not used for offers from chess program */
15400     }
15401 }
15402
15403 void
15404 RematchEvent ()
15405 {
15406     /* Issue ICS rematch command */
15407     if (appData.icsActive) {
15408         SendToICS(ics_prefix);
15409         SendToICS("rematch\n");
15410     }
15411 }
15412
15413 void
15414 CallFlagEvent ()
15415 {
15416     /* Call your opponent's flag (claim a win on time) */
15417     if (appData.icsActive) {
15418         SendToICS(ics_prefix);
15419         SendToICS("flag\n");
15420     } else {
15421         switch (gameMode) {
15422           default:
15423             return;
15424           case MachinePlaysWhite:
15425             if (whiteFlag) {
15426                 if (blackFlag)
15427                   GameEnds(GameIsDrawn, "Both players ran out of time",
15428                            GE_PLAYER);
15429                 else
15430                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15431             } else {
15432                 DisplayError(_("Your opponent is not out of time"), 0);
15433             }
15434             break;
15435           case MachinePlaysBlack:
15436             if (blackFlag) {
15437                 if (whiteFlag)
15438                   GameEnds(GameIsDrawn, "Both players ran out of time",
15439                            GE_PLAYER);
15440                 else
15441                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15442             } else {
15443                 DisplayError(_("Your opponent is not out of time"), 0);
15444             }
15445             break;
15446         }
15447     }
15448 }
15449
15450 void
15451 ClockClick (int which)
15452 {       // [HGM] code moved to back-end from winboard.c
15453         if(which) { // black clock
15454           if (gameMode == EditPosition || gameMode == IcsExamining) {
15455             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15456             SetBlackToPlayEvent();
15457           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15458                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15459           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15460           } else if (shiftKey) {
15461             AdjustClock(which, -1);
15462           } else if (gameMode == IcsPlayingWhite ||
15463                      gameMode == MachinePlaysBlack) {
15464             CallFlagEvent();
15465           }
15466         } else { // white clock
15467           if (gameMode == EditPosition || gameMode == IcsExamining) {
15468             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15469             SetWhiteToPlayEvent();
15470           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15471                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15472           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15473           } else if (shiftKey) {
15474             AdjustClock(which, -1);
15475           } else if (gameMode == IcsPlayingBlack ||
15476                    gameMode == MachinePlaysWhite) {
15477             CallFlagEvent();
15478           }
15479         }
15480 }
15481
15482 void
15483 DrawEvent ()
15484 {
15485     /* Offer draw or accept pending draw offer from opponent */
15486
15487     if (appData.icsActive) {
15488         /* Note: tournament rules require draw offers to be
15489            made after you make your move but before you punch
15490            your clock.  Currently ICS doesn't let you do that;
15491            instead, you immediately punch your clock after making
15492            a move, but you can offer a draw at any time. */
15493
15494         SendToICS(ics_prefix);
15495         SendToICS("draw\n");
15496         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15497     } else if (cmailMsgLoaded) {
15498         if (currentMove == cmailOldMove &&
15499             commentList[cmailOldMove] != NULL &&
15500             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15501                    "Black offers a draw" : "White offers a draw")) {
15502             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15503             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15504         } else if (currentMove == cmailOldMove + 1) {
15505             char *offer = WhiteOnMove(cmailOldMove) ?
15506               "White offers a draw" : "Black offers a draw";
15507             AppendComment(currentMove, offer, TRUE);
15508             DisplayComment(currentMove - 1, offer);
15509             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15510         } else {
15511             DisplayError(_("You must make your move before offering a draw"), 0);
15512             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15513         }
15514     } else if (first.offeredDraw) {
15515         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15516     } else {
15517         if (first.sendDrawOffers) {
15518             SendToProgram("draw\n", &first);
15519             userOfferedDraw = TRUE;
15520         }
15521     }
15522 }
15523
15524 void
15525 AdjournEvent ()
15526 {
15527     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15528
15529     if (appData.icsActive) {
15530         SendToICS(ics_prefix);
15531         SendToICS("adjourn\n");
15532     } else {
15533         /* Currently GNU Chess doesn't offer or accept Adjourns */
15534     }
15535 }
15536
15537
15538 void
15539 AbortEvent ()
15540 {
15541     /* Offer Abort or accept pending Abort offer from opponent */
15542
15543     if (appData.icsActive) {
15544         SendToICS(ics_prefix);
15545         SendToICS("abort\n");
15546     } else {
15547         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15548     }
15549 }
15550
15551 void
15552 ResignEvent ()
15553 {
15554     /* Resign.  You can do this even if it's not your turn. */
15555
15556     if (appData.icsActive) {
15557         SendToICS(ics_prefix);
15558         SendToICS("resign\n");
15559     } else {
15560         switch (gameMode) {
15561           case MachinePlaysWhite:
15562             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15563             break;
15564           case MachinePlaysBlack:
15565             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15566             break;
15567           case EditGame:
15568             if (cmailMsgLoaded) {
15569                 TruncateGame();
15570                 if (WhiteOnMove(cmailOldMove)) {
15571                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15572                 } else {
15573                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15574                 }
15575                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15576             }
15577             break;
15578           default:
15579             break;
15580         }
15581     }
15582 }
15583
15584
15585 void
15586 StopObservingEvent ()
15587 {
15588     /* Stop observing current games */
15589     SendToICS(ics_prefix);
15590     SendToICS("unobserve\n");
15591 }
15592
15593 void
15594 StopExaminingEvent ()
15595 {
15596     /* Stop observing current game */
15597     SendToICS(ics_prefix);
15598     SendToICS("unexamine\n");
15599 }
15600
15601 void
15602 ForwardInner (int target)
15603 {
15604     int limit; int oldSeekGraphUp = seekGraphUp;
15605
15606     if (appData.debugMode)
15607         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15608                 target, currentMove, forwardMostMove);
15609
15610     if (gameMode == EditPosition)
15611       return;
15612
15613     seekGraphUp = FALSE;
15614     MarkTargetSquares(1);
15615     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15616
15617     if (gameMode == PlayFromGameFile && !pausing)
15618       PauseEvent();
15619
15620     if (gameMode == IcsExamining && pausing)
15621       limit = pauseExamForwardMostMove;
15622     else
15623       limit = forwardMostMove;
15624
15625     if (target > limit) target = limit;
15626
15627     if (target > 0 && moveList[target - 1][0]) {
15628         int fromX, fromY, toX, toY;
15629         toX = moveList[target - 1][2] - AAA;
15630         toY = moveList[target - 1][3] - ONE;
15631         if (moveList[target - 1][1] == '@') {
15632             if (appData.highlightLastMove) {
15633                 SetHighlights(-1, -1, toX, toY);
15634             }
15635         } else {
15636             int viaX = moveList[target - 1][5] - AAA;
15637             int viaY = moveList[target - 1][6] - ONE;
15638             fromX = moveList[target - 1][0] - AAA;
15639             fromY = moveList[target - 1][1] - ONE;
15640             if (target == currentMove + 1) {
15641                 if(moveList[target - 1][4] == ';') { // multi-leg
15642                     ChessSquare piece = boards[currentMove][viaY][viaX];
15643                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15644                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15645                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15646                     boards[currentMove][viaY][viaX] = piece;
15647                 } else
15648                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15649             }
15650             if (appData.highlightLastMove) {
15651                 SetHighlights(fromX, fromY, toX, toY);
15652             }
15653         }
15654     }
15655     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15656         gameMode == Training || gameMode == PlayFromGameFile ||
15657         gameMode == AnalyzeFile) {
15658         while (currentMove < target) {
15659             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15660             SendMoveToProgram(currentMove++, &first);
15661         }
15662     } else {
15663         currentMove = target;
15664     }
15665
15666     if (gameMode == EditGame || gameMode == EndOfGame) {
15667         whiteTimeRemaining = timeRemaining[0][currentMove];
15668         blackTimeRemaining = timeRemaining[1][currentMove];
15669     }
15670     DisplayBothClocks();
15671     DisplayMove(currentMove - 1);
15672     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15673     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15674     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15675         DisplayComment(currentMove - 1, commentList[currentMove]);
15676     }
15677     ClearMap(); // [HGM] exclude: invalidate map
15678 }
15679
15680
15681 void
15682 ForwardEvent ()
15683 {
15684     if (gameMode == IcsExamining && !pausing) {
15685         SendToICS(ics_prefix);
15686         SendToICS("forward\n");
15687     } else {
15688         ForwardInner(currentMove + 1);
15689     }
15690 }
15691
15692 void
15693 ToEndEvent ()
15694 {
15695     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15696         /* to optimze, we temporarily turn off analysis mode while we feed
15697          * the remaining moves to the engine. Otherwise we get analysis output
15698          * after each move.
15699          */
15700         if (first.analysisSupport) {
15701           SendToProgram("exit\nforce\n", &first);
15702           first.analyzing = FALSE;
15703         }
15704     }
15705
15706     if (gameMode == IcsExamining && !pausing) {
15707         SendToICS(ics_prefix);
15708         SendToICS("forward 999999\n");
15709     } else {
15710         ForwardInner(forwardMostMove);
15711     }
15712
15713     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15714         /* we have fed all the moves, so reactivate analysis mode */
15715         SendToProgram("analyze\n", &first);
15716         first.analyzing = TRUE;
15717         /*first.maybeThinking = TRUE;*/
15718         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15719     }
15720 }
15721
15722 void
15723 BackwardInner (int target)
15724 {
15725     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15726
15727     if (appData.debugMode)
15728         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15729                 target, currentMove, forwardMostMove);
15730
15731     if (gameMode == EditPosition) return;
15732     seekGraphUp = FALSE;
15733     MarkTargetSquares(1);
15734     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15735     if (currentMove <= backwardMostMove) {
15736         ClearHighlights();
15737         DrawPosition(full_redraw, boards[currentMove]);
15738         return;
15739     }
15740     if (gameMode == PlayFromGameFile && !pausing)
15741       PauseEvent();
15742
15743     if (moveList[target][0]) {
15744         int fromX, fromY, toX, toY;
15745         toX = moveList[target][2] - AAA;
15746         toY = moveList[target][3] - ONE;
15747         if (moveList[target][1] == '@') {
15748             if (appData.highlightLastMove) {
15749                 SetHighlights(-1, -1, toX, toY);
15750             }
15751         } else {
15752             fromX = moveList[target][0] - AAA;
15753             fromY = moveList[target][1] - ONE;
15754             if (target == currentMove - 1) {
15755                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15756             }
15757             if (appData.highlightLastMove) {
15758                 SetHighlights(fromX, fromY, toX, toY);
15759             }
15760         }
15761     }
15762     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15763         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15764         while (currentMove > target) {
15765             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15766                 // null move cannot be undone. Reload program with move history before it.
15767                 int i;
15768                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15769                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15770                 }
15771                 SendBoard(&first, i);
15772               if(second.analyzing) SendBoard(&second, i);
15773                 for(currentMove=i; currentMove<target; currentMove++) {
15774                     SendMoveToProgram(currentMove, &first);
15775                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15776                 }
15777                 break;
15778             }
15779             SendToBoth("undo\n");
15780             currentMove--;
15781         }
15782     } else {
15783         currentMove = target;
15784     }
15785
15786     if (gameMode == EditGame || gameMode == EndOfGame) {
15787         whiteTimeRemaining = timeRemaining[0][currentMove];
15788         blackTimeRemaining = timeRemaining[1][currentMove];
15789     }
15790     DisplayBothClocks();
15791     DisplayMove(currentMove - 1);
15792     DrawPosition(full_redraw, boards[currentMove]);
15793     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15794     // [HGM] PV info: routine tests if comment empty
15795     DisplayComment(currentMove - 1, commentList[currentMove]);
15796     ClearMap(); // [HGM] exclude: invalidate map
15797 }
15798
15799 void
15800 BackwardEvent ()
15801 {
15802     if (gameMode == IcsExamining && !pausing) {
15803         SendToICS(ics_prefix);
15804         SendToICS("backward\n");
15805     } else {
15806         BackwardInner(currentMove - 1);
15807     }
15808 }
15809
15810 void
15811 ToStartEvent ()
15812 {
15813     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15814         /* to optimize, we temporarily turn off analysis mode while we undo
15815          * all the moves. Otherwise we get analysis output after each undo.
15816          */
15817         if (first.analysisSupport) {
15818           SendToProgram("exit\nforce\n", &first);
15819           first.analyzing = FALSE;
15820         }
15821     }
15822
15823     if (gameMode == IcsExamining && !pausing) {
15824         SendToICS(ics_prefix);
15825         SendToICS("backward 999999\n");
15826     } else {
15827         BackwardInner(backwardMostMove);
15828     }
15829
15830     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15831         /* we have fed all the moves, so reactivate analysis mode */
15832         SendToProgram("analyze\n", &first);
15833         first.analyzing = TRUE;
15834         /*first.maybeThinking = TRUE;*/
15835         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15836     }
15837 }
15838
15839 void
15840 ToNrEvent (int to)
15841 {
15842   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15843   if (to >= forwardMostMove) to = forwardMostMove;
15844   if (to <= backwardMostMove) to = backwardMostMove;
15845   if (to < currentMove) {
15846     BackwardInner(to);
15847   } else {
15848     ForwardInner(to);
15849   }
15850 }
15851
15852 void
15853 RevertEvent (Boolean annotate)
15854 {
15855     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15856         return;
15857     }
15858     if (gameMode != IcsExamining) {
15859         DisplayError(_("You are not examining a game"), 0);
15860         return;
15861     }
15862     if (pausing) {
15863         DisplayError(_("You can't revert while pausing"), 0);
15864         return;
15865     }
15866     SendToICS(ics_prefix);
15867     SendToICS("revert\n");
15868 }
15869
15870 void
15871 RetractMoveEvent ()
15872 {
15873     switch (gameMode) {
15874       case MachinePlaysWhite:
15875       case MachinePlaysBlack:
15876         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15877             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15878             return;
15879         }
15880         if (forwardMostMove < 2) return;
15881         currentMove = forwardMostMove = forwardMostMove - 2;
15882         whiteTimeRemaining = timeRemaining[0][currentMove];
15883         blackTimeRemaining = timeRemaining[1][currentMove];
15884         DisplayBothClocks();
15885         DisplayMove(currentMove - 1);
15886         ClearHighlights();/*!! could figure this out*/
15887         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15888         SendToProgram("remove\n", &first);
15889         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15890         break;
15891
15892       case BeginningOfGame:
15893       default:
15894         break;
15895
15896       case IcsPlayingWhite:
15897       case IcsPlayingBlack:
15898         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15899             SendToICS(ics_prefix);
15900             SendToICS("takeback 2\n");
15901         } else {
15902             SendToICS(ics_prefix);
15903             SendToICS("takeback 1\n");
15904         }
15905         break;
15906     }
15907 }
15908
15909 void
15910 MoveNowEvent ()
15911 {
15912     ChessProgramState *cps;
15913
15914     switch (gameMode) {
15915       case MachinePlaysWhite:
15916         if (!WhiteOnMove(forwardMostMove)) {
15917             DisplayError(_("It is your turn"), 0);
15918             return;
15919         }
15920         cps = &first;
15921         break;
15922       case MachinePlaysBlack:
15923         if (WhiteOnMove(forwardMostMove)) {
15924             DisplayError(_("It is your turn"), 0);
15925             return;
15926         }
15927         cps = &first;
15928         break;
15929       case TwoMachinesPlay:
15930         if (WhiteOnMove(forwardMostMove) ==
15931             (first.twoMachinesColor[0] == 'w')) {
15932             cps = &first;
15933         } else {
15934             cps = &second;
15935         }
15936         break;
15937       case BeginningOfGame:
15938       default:
15939         return;
15940     }
15941     SendToProgram("?\n", cps);
15942 }
15943
15944 void
15945 TruncateGameEvent ()
15946 {
15947     EditGameEvent();
15948     if (gameMode != EditGame) return;
15949     TruncateGame();
15950 }
15951
15952 void
15953 TruncateGame ()
15954 {
15955     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15956     if (forwardMostMove > currentMove) {
15957         if (gameInfo.resultDetails != NULL) {
15958             free(gameInfo.resultDetails);
15959             gameInfo.resultDetails = NULL;
15960             gameInfo.result = GameUnfinished;
15961         }
15962         forwardMostMove = currentMove;
15963         HistorySet(parseList, backwardMostMove, forwardMostMove,
15964                    currentMove-1);
15965     }
15966 }
15967
15968 void
15969 HintEvent ()
15970 {
15971     if (appData.noChessProgram) return;
15972     switch (gameMode) {
15973       case MachinePlaysWhite:
15974         if (WhiteOnMove(forwardMostMove)) {
15975             DisplayError(_("Wait until your turn."), 0);
15976             return;
15977         }
15978         break;
15979       case BeginningOfGame:
15980       case MachinePlaysBlack:
15981         if (!WhiteOnMove(forwardMostMove)) {
15982             DisplayError(_("Wait until your turn."), 0);
15983             return;
15984         }
15985         break;
15986       default:
15987         DisplayError(_("No hint available"), 0);
15988         return;
15989     }
15990     SendToProgram("hint\n", &first);
15991     hintRequested = TRUE;
15992 }
15993
15994 int
15995 SaveSelected (FILE *g, int dummy, char *dummy2)
15996 {
15997     ListGame * lg = (ListGame *) gameList.head;
15998     int nItem, cnt=0;
15999     FILE *f;
16000
16001     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16002         DisplayError(_("Game list not loaded or empty"), 0);
16003         return 0;
16004     }
16005
16006     creatingBook = TRUE; // suppresses stuff during load game
16007
16008     /* Get list size */
16009     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16010         if(lg->position >= 0) { // selected?
16011             LoadGame(f, nItem, "", TRUE);
16012             SaveGamePGN2(g); // leaves g open
16013             cnt++; DoEvents();
16014         }
16015         lg = (ListGame *) lg->node.succ;
16016     }
16017
16018     fclose(g);
16019     creatingBook = FALSE;
16020
16021     return cnt;
16022 }
16023
16024 void
16025 CreateBookEvent ()
16026 {
16027     ListGame * lg = (ListGame *) gameList.head;
16028     FILE *f, *g;
16029     int nItem;
16030     static int secondTime = FALSE;
16031
16032     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16033         DisplayError(_("Game list not loaded or empty"), 0);
16034         return;
16035     }
16036
16037     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16038         fclose(g);
16039         secondTime++;
16040         DisplayNote(_("Book file exists! Try again for overwrite."));
16041         return;
16042     }
16043
16044     creatingBook = TRUE;
16045     secondTime = FALSE;
16046
16047     /* Get list size */
16048     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16049         if(lg->position >= 0) {
16050             LoadGame(f, nItem, "", TRUE);
16051             AddGameToBook(TRUE);
16052             DoEvents();
16053         }
16054         lg = (ListGame *) lg->node.succ;
16055     }
16056
16057     creatingBook = FALSE;
16058     FlushBook();
16059 }
16060
16061 void
16062 BookEvent ()
16063 {
16064     if (appData.noChessProgram) return;
16065     switch (gameMode) {
16066       case MachinePlaysWhite:
16067         if (WhiteOnMove(forwardMostMove)) {
16068             DisplayError(_("Wait until your turn."), 0);
16069             return;
16070         }
16071         break;
16072       case BeginningOfGame:
16073       case MachinePlaysBlack:
16074         if (!WhiteOnMove(forwardMostMove)) {
16075             DisplayError(_("Wait until your turn."), 0);
16076             return;
16077         }
16078         break;
16079       case EditPosition:
16080         EditPositionDone(TRUE);
16081         break;
16082       case TwoMachinesPlay:
16083         return;
16084       default:
16085         break;
16086     }
16087     SendToProgram("bk\n", &first);
16088     bookOutput[0] = NULLCHAR;
16089     bookRequested = TRUE;
16090 }
16091
16092 void
16093 AboutGameEvent ()
16094 {
16095     char *tags = PGNTags(&gameInfo);
16096     TagsPopUp(tags, CmailMsg());
16097     free(tags);
16098 }
16099
16100 /* end button procedures */
16101
16102 void
16103 PrintPosition (FILE *fp, int move)
16104 {
16105     int i, j;
16106
16107     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16108         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16109             char c = PieceToChar(boards[move][i][j]);
16110             fputc(c == 'x' ? '.' : c, fp);
16111             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16112         }
16113     }
16114     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16115       fprintf(fp, "white to play\n");
16116     else
16117       fprintf(fp, "black to play\n");
16118 }
16119
16120 void
16121 PrintOpponents (FILE *fp)
16122 {
16123     if (gameInfo.white != NULL) {
16124         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16125     } else {
16126         fprintf(fp, "\n");
16127     }
16128 }
16129
16130 /* Find last component of program's own name, using some heuristics */
16131 void
16132 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16133 {
16134     char *p, *q, c;
16135     int local = (strcmp(host, "localhost") == 0);
16136     while (!local && (p = strchr(prog, ';')) != NULL) {
16137         p++;
16138         while (*p == ' ') p++;
16139         prog = p;
16140     }
16141     if (*prog == '"' || *prog == '\'') {
16142         q = strchr(prog + 1, *prog);
16143     } else {
16144         q = strchr(prog, ' ');
16145     }
16146     if (q == NULL) q = prog + strlen(prog);
16147     p = q;
16148     while (p >= prog && *p != '/' && *p != '\\') p--;
16149     p++;
16150     if(p == prog && *p == '"') p++;
16151     c = *q; *q = 0;
16152     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16153     memcpy(buf, p, q - p);
16154     buf[q - p] = NULLCHAR;
16155     if (!local) {
16156         strcat(buf, "@");
16157         strcat(buf, host);
16158     }
16159 }
16160
16161 char *
16162 TimeControlTagValue ()
16163 {
16164     char buf[MSG_SIZ];
16165     if (!appData.clockMode) {
16166       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16167     } else if (movesPerSession > 0) {
16168       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16169     } else if (timeIncrement == 0) {
16170       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16171     } else {
16172       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16173     }
16174     return StrSave(buf);
16175 }
16176
16177 void
16178 SetGameInfo ()
16179 {
16180     /* This routine is used only for certain modes */
16181     VariantClass v = gameInfo.variant;
16182     ChessMove r = GameUnfinished;
16183     char *p = NULL;
16184
16185     if(keepInfo) return;
16186
16187     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16188         r = gameInfo.result;
16189         p = gameInfo.resultDetails;
16190         gameInfo.resultDetails = NULL;
16191     }
16192     ClearGameInfo(&gameInfo);
16193     gameInfo.variant = v;
16194
16195     switch (gameMode) {
16196       case MachinePlaysWhite:
16197         gameInfo.event = StrSave( appData.pgnEventHeader );
16198         gameInfo.site = StrSave(HostName());
16199         gameInfo.date = PGNDate();
16200         gameInfo.round = StrSave("-");
16201         gameInfo.white = StrSave(first.tidy);
16202         gameInfo.black = StrSave(UserName());
16203         gameInfo.timeControl = TimeControlTagValue();
16204         break;
16205
16206       case MachinePlaysBlack:
16207         gameInfo.event = StrSave( appData.pgnEventHeader );
16208         gameInfo.site = StrSave(HostName());
16209         gameInfo.date = PGNDate();
16210         gameInfo.round = StrSave("-");
16211         gameInfo.white = StrSave(UserName());
16212         gameInfo.black = StrSave(first.tidy);
16213         gameInfo.timeControl = TimeControlTagValue();
16214         break;
16215
16216       case TwoMachinesPlay:
16217         gameInfo.event = StrSave( appData.pgnEventHeader );
16218         gameInfo.site = StrSave(HostName());
16219         gameInfo.date = PGNDate();
16220         if (roundNr > 0) {
16221             char buf[MSG_SIZ];
16222             snprintf(buf, MSG_SIZ, "%d", roundNr);
16223             gameInfo.round = StrSave(buf);
16224         } else {
16225             gameInfo.round = StrSave("-");
16226         }
16227         if (first.twoMachinesColor[0] == 'w') {
16228             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16229             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16230         } else {
16231             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16232             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16233         }
16234         gameInfo.timeControl = TimeControlTagValue();
16235         break;
16236
16237       case EditGame:
16238         gameInfo.event = StrSave("Edited game");
16239         gameInfo.site = StrSave(HostName());
16240         gameInfo.date = PGNDate();
16241         gameInfo.round = StrSave("-");
16242         gameInfo.white = StrSave("-");
16243         gameInfo.black = StrSave("-");
16244         gameInfo.result = r;
16245         gameInfo.resultDetails = p;
16246         break;
16247
16248       case EditPosition:
16249         gameInfo.event = StrSave("Edited position");
16250         gameInfo.site = StrSave(HostName());
16251         gameInfo.date = PGNDate();
16252         gameInfo.round = StrSave("-");
16253         gameInfo.white = StrSave("-");
16254         gameInfo.black = StrSave("-");
16255         break;
16256
16257       case IcsPlayingWhite:
16258       case IcsPlayingBlack:
16259       case IcsObserving:
16260       case IcsExamining:
16261         break;
16262
16263       case PlayFromGameFile:
16264         gameInfo.event = StrSave("Game from non-PGN file");
16265         gameInfo.site = StrSave(HostName());
16266         gameInfo.date = PGNDate();
16267         gameInfo.round = StrSave("-");
16268         gameInfo.white = StrSave("?");
16269         gameInfo.black = StrSave("?");
16270         break;
16271
16272       default:
16273         break;
16274     }
16275 }
16276
16277 void
16278 ReplaceComment (int index, char *text)
16279 {
16280     int len;
16281     char *p;
16282     float score;
16283
16284     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16285        pvInfoList[index-1].depth == len &&
16286        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16287        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16288     while (*text == '\n') text++;
16289     len = strlen(text);
16290     while (len > 0 && text[len - 1] == '\n') len--;
16291
16292     if (commentList[index] != NULL)
16293       free(commentList[index]);
16294
16295     if (len == 0) {
16296         commentList[index] = NULL;
16297         return;
16298     }
16299   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16300       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16301       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16302     commentList[index] = (char *) malloc(len + 2);
16303     strncpy(commentList[index], text, len);
16304     commentList[index][len] = '\n';
16305     commentList[index][len + 1] = NULLCHAR;
16306   } else {
16307     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16308     char *p;
16309     commentList[index] = (char *) malloc(len + 7);
16310     safeStrCpy(commentList[index], "{\n", 3);
16311     safeStrCpy(commentList[index]+2, text, len+1);
16312     commentList[index][len+2] = NULLCHAR;
16313     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16314     strcat(commentList[index], "\n}\n");
16315   }
16316 }
16317
16318 void
16319 CrushCRs (char *text)
16320 {
16321   char *p = text;
16322   char *q = text;
16323   char ch;
16324
16325   do {
16326     ch = *p++;
16327     if (ch == '\r') continue;
16328     *q++ = ch;
16329   } while (ch != '\0');
16330 }
16331
16332 void
16333 AppendComment (int index, char *text, Boolean addBraces)
16334 /* addBraces  tells if we should add {} */
16335 {
16336     int oldlen, len;
16337     char *old;
16338
16339 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16340     if(addBraces == 3) addBraces = 0; else // force appending literally
16341     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16342
16343     CrushCRs(text);
16344     while (*text == '\n') text++;
16345     len = strlen(text);
16346     while (len > 0 && text[len - 1] == '\n') len--;
16347     text[len] = NULLCHAR;
16348
16349     if (len == 0) return;
16350
16351     if (commentList[index] != NULL) {
16352       Boolean addClosingBrace = addBraces;
16353         old = commentList[index];
16354         oldlen = strlen(old);
16355         while(commentList[index][oldlen-1] ==  '\n')
16356           commentList[index][--oldlen] = NULLCHAR;
16357         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16358         safeStrCpy(commentList[index], old, oldlen + len + 6);
16359         free(old);
16360         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16361         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16362           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16363           while (*text == '\n') { text++; len--; }
16364           commentList[index][--oldlen] = NULLCHAR;
16365       }
16366         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16367         else          strcat(commentList[index], "\n");
16368         strcat(commentList[index], text);
16369         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16370         else          strcat(commentList[index], "\n");
16371     } else {
16372         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16373         if(addBraces)
16374           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16375         else commentList[index][0] = NULLCHAR;
16376         strcat(commentList[index], text);
16377         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16378         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16379     }
16380 }
16381
16382 static char *
16383 FindStr (char * text, char * sub_text)
16384 {
16385     char * result = strstr( text, sub_text );
16386
16387     if( result != NULL ) {
16388         result += strlen( sub_text );
16389     }
16390
16391     return result;
16392 }
16393
16394 /* [AS] Try to extract PV info from PGN comment */
16395 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16396 char *
16397 GetInfoFromComment (int index, char * text)
16398 {
16399     char * sep = text, *p;
16400
16401     if( text != NULL && index > 0 ) {
16402         int score = 0;
16403         int depth = 0;
16404         int time = -1, sec = 0, deci;
16405         char * s_eval = FindStr( text, "[%eval " );
16406         char * s_emt = FindStr( text, "[%emt " );
16407 #if 0
16408         if( s_eval != NULL || s_emt != NULL ) {
16409 #else
16410         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16411 #endif
16412             /* New style */
16413             char delim;
16414
16415             if( s_eval != NULL ) {
16416                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16417                     return text;
16418                 }
16419
16420                 if( delim != ']' ) {
16421                     return text;
16422                 }
16423             }
16424
16425             if( s_emt != NULL ) {
16426             }
16427                 return text;
16428         }
16429         else {
16430             /* We expect something like: [+|-]nnn.nn/dd */
16431             int score_lo = 0;
16432
16433             if(*text != '{') return text; // [HGM] braces: must be normal comment
16434
16435             sep = strchr( text, '/' );
16436             if( sep == NULL || sep < (text+4) ) {
16437                 return text;
16438             }
16439
16440             p = text;
16441             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16442             if(p[1] == '(') { // comment starts with PV
16443                p = strchr(p, ')'); // locate end of PV
16444                if(p == NULL || sep < p+5) return text;
16445                // at this point we have something like "{(.*) +0.23/6 ..."
16446                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16447                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16448                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16449             }
16450             time = -1; sec = -1; deci = -1;
16451             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16452                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16453                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16454                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16455                 return text;
16456             }
16457
16458             if( score_lo < 0 || score_lo >= 100 ) {
16459                 return text;
16460             }
16461
16462             if(sec >= 0) time = 600*time + 10*sec; else
16463             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16464
16465             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16466
16467             /* [HGM] PV time: now locate end of PV info */
16468             while( *++sep >= '0' && *sep <= '9'); // strip depth
16469             if(time >= 0)
16470             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16471             if(sec >= 0)
16472             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16473             if(deci >= 0)
16474             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16475             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16476         }
16477
16478         if( depth <= 0 ) {
16479             return text;
16480         }
16481
16482         if( time < 0 ) {
16483             time = -1;
16484         }
16485
16486         pvInfoList[index-1].depth = depth;
16487         pvInfoList[index-1].score = score;
16488         pvInfoList[index-1].time  = 10*time; // centi-sec
16489         if(*sep == '}') *sep = 0; else *--sep = '{';
16490         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16491     }
16492     return sep;
16493 }
16494
16495 void
16496 SendToProgram (char *message, ChessProgramState *cps)
16497 {
16498     int count, outCount, error;
16499     char buf[MSG_SIZ];
16500
16501     if (cps->pr == NoProc) return;
16502     Attention(cps);
16503
16504     if (appData.debugMode) {
16505         TimeMark now;
16506         GetTimeMark(&now);
16507         fprintf(debugFP, "%ld >%-6s: %s",
16508                 SubtractTimeMarks(&now, &programStartTime),
16509                 cps->which, message);
16510         if(serverFP)
16511             fprintf(serverFP, "%ld >%-6s: %s",
16512                 SubtractTimeMarks(&now, &programStartTime),
16513                 cps->which, message), fflush(serverFP);
16514     }
16515
16516     count = strlen(message);
16517     outCount = OutputToProcess(cps->pr, message, count, &error);
16518     if (outCount < count && !exiting
16519                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16520       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16521       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16522         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16523             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16524                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16525                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16526                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16527             } else {
16528                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16529                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16530                 gameInfo.result = res;
16531             }
16532             gameInfo.resultDetails = StrSave(buf);
16533         }
16534         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16535         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16536     }
16537 }
16538
16539 void
16540 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16541 {
16542     char *end_str;
16543     char buf[MSG_SIZ];
16544     ChessProgramState *cps = (ChessProgramState *)closure;
16545
16546     if (isr != cps->isr) return; /* Killed intentionally */
16547     if (count <= 0) {
16548         if (count == 0) {
16549             RemoveInputSource(cps->isr);
16550             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16551                     _(cps->which), cps->program);
16552             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16553             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16554                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16555                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16556                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16557                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16558                 } else {
16559                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16560                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16561                     gameInfo.result = res;
16562                 }
16563                 gameInfo.resultDetails = StrSave(buf);
16564             }
16565             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16566             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16567         } else {
16568             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16569                     _(cps->which), cps->program);
16570             RemoveInputSource(cps->isr);
16571
16572             /* [AS] Program is misbehaving badly... kill it */
16573             if( count == -2 ) {
16574                 DestroyChildProcess( cps->pr, 9 );
16575                 cps->pr = NoProc;
16576             }
16577
16578             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16579         }
16580         return;
16581     }
16582
16583     if ((end_str = strchr(message, '\r')) != NULL)
16584       *end_str = NULLCHAR;
16585     if ((end_str = strchr(message, '\n')) != NULL)
16586       *end_str = NULLCHAR;
16587
16588     if (appData.debugMode) {
16589         TimeMark now; int print = 1;
16590         char *quote = ""; char c; int i;
16591
16592         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16593                 char start = message[0];
16594                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16595                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16596                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16597                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16598                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16599                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16600                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16601                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16602                    sscanf(message, "hint: %c", &c)!=1 &&
16603                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16604                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16605                     print = (appData.engineComments >= 2);
16606                 }
16607                 message[0] = start; // restore original message
16608         }
16609         if(print) {
16610                 GetTimeMark(&now);
16611                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16612                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16613                         quote,
16614                         message);
16615                 if(serverFP)
16616                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16617                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16618                         quote,
16619                         message), fflush(serverFP);
16620         }
16621     }
16622
16623     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16624     if (appData.icsEngineAnalyze) {
16625         if (strstr(message, "whisper") != NULL ||
16626              strstr(message, "kibitz") != NULL ||
16627             strstr(message, "tellics") != NULL) return;
16628     }
16629
16630     HandleMachineMove(message, cps);
16631 }
16632
16633
16634 void
16635 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16636 {
16637     char buf[MSG_SIZ];
16638     int seconds;
16639
16640     if( timeControl_2 > 0 ) {
16641         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16642             tc = timeControl_2;
16643         }
16644     }
16645     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16646     inc /= cps->timeOdds;
16647     st  /= cps->timeOdds;
16648
16649     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16650
16651     if (st > 0) {
16652       /* Set exact time per move, normally using st command */
16653       if (cps->stKludge) {
16654         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16655         seconds = st % 60;
16656         if (seconds == 0) {
16657           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16658         } else {
16659           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16660         }
16661       } else {
16662         snprintf(buf, MSG_SIZ, "st %d\n", st);
16663       }
16664     } else {
16665       /* Set conventional or incremental time control, using level command */
16666       if (seconds == 0) {
16667         /* Note old gnuchess bug -- minutes:seconds used to not work.
16668            Fixed in later versions, but still avoid :seconds
16669            when seconds is 0. */
16670         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16671       } else {
16672         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16673                  seconds, inc/1000.);
16674       }
16675     }
16676     SendToProgram(buf, cps);
16677
16678     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16679     /* Orthogonally, limit search to given depth */
16680     if (sd > 0) {
16681       if (cps->sdKludge) {
16682         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16683       } else {
16684         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16685       }
16686       SendToProgram(buf, cps);
16687     }
16688
16689     if(cps->nps >= 0) { /* [HGM] nps */
16690         if(cps->supportsNPS == FALSE)
16691           cps->nps = -1; // don't use if engine explicitly says not supported!
16692         else {
16693           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16694           SendToProgram(buf, cps);
16695         }
16696     }
16697 }
16698
16699 ChessProgramState *
16700 WhitePlayer ()
16701 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16702 {
16703     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16704        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16705         return &second;
16706     return &first;
16707 }
16708
16709 void
16710 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16711 {
16712     char message[MSG_SIZ];
16713     long time, otime;
16714
16715     /* Note: this routine must be called when the clocks are stopped
16716        or when they have *just* been set or switched; otherwise
16717        it will be off by the time since the current tick started.
16718     */
16719     if (machineWhite) {
16720         time = whiteTimeRemaining / 10;
16721         otime = blackTimeRemaining / 10;
16722     } else {
16723         time = blackTimeRemaining / 10;
16724         otime = whiteTimeRemaining / 10;
16725     }
16726     /* [HGM] translate opponent's time by time-odds factor */
16727     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16728
16729     if (time <= 0) time = 1;
16730     if (otime <= 0) otime = 1;
16731
16732     snprintf(message, MSG_SIZ, "time %ld\n", time);
16733     SendToProgram(message, cps);
16734
16735     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16736     SendToProgram(message, cps);
16737 }
16738
16739 char *
16740 EngineDefinedVariant (ChessProgramState *cps, int n)
16741 {   // return name of n-th unknown variant that engine supports
16742     static char buf[MSG_SIZ];
16743     char *p, *s = cps->variants;
16744     if(!s) return NULL;
16745     do { // parse string from variants feature
16746       VariantClass v;
16747         p = strchr(s, ',');
16748         if(p) *p = NULLCHAR;
16749       v = StringToVariant(s);
16750       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16751         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16752             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16753                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16754                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16755                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16756             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16757         }
16758         if(p) *p++ = ',';
16759         if(n < 0) return buf;
16760     } while(s = p);
16761     return NULL;
16762 }
16763
16764 int
16765 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16766 {
16767   char buf[MSG_SIZ];
16768   int len = strlen(name);
16769   int val;
16770
16771   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16772     (*p) += len + 1;
16773     sscanf(*p, "%d", &val);
16774     *loc = (val != 0);
16775     while (**p && **p != ' ')
16776       (*p)++;
16777     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16778     SendToProgram(buf, cps);
16779     return TRUE;
16780   }
16781   return FALSE;
16782 }
16783
16784 int
16785 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16786 {
16787   char buf[MSG_SIZ];
16788   int len = strlen(name);
16789   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16790     (*p) += len + 1;
16791     sscanf(*p, "%d", loc);
16792     while (**p && **p != ' ') (*p)++;
16793     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16794     SendToProgram(buf, cps);
16795     return TRUE;
16796   }
16797   return FALSE;
16798 }
16799
16800 int
16801 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16802 {
16803   char buf[MSG_SIZ];
16804   int len = strlen(name);
16805   if (strncmp((*p), name, len) == 0
16806       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16807     (*p) += len + 2;
16808     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16809     sscanf(*p, "%[^\"]", *loc);
16810     while (**p && **p != '\"') (*p)++;
16811     if (**p == '\"') (*p)++;
16812     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16813     SendToProgram(buf, cps);
16814     return TRUE;
16815   }
16816   return FALSE;
16817 }
16818
16819 int
16820 ParseOption (Option *opt, ChessProgramState *cps)
16821 // [HGM] options: process the string that defines an engine option, and determine
16822 // name, type, default value, and allowed value range
16823 {
16824         char *p, *q, buf[MSG_SIZ];
16825         int n, min = (-1)<<31, max = 1<<31, def;
16826
16827         if(p = strstr(opt->name, " -spin ")) {
16828             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16829             if(max < min) max = min; // enforce consistency
16830             if(def < min) def = min;
16831             if(def > max) def = max;
16832             opt->value = def;
16833             opt->min = min;
16834             opt->max = max;
16835             opt->type = Spin;
16836         } else if((p = strstr(opt->name, " -slider "))) {
16837             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16838             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16839             if(max < min) max = min; // enforce consistency
16840             if(def < min) def = min;
16841             if(def > max) def = max;
16842             opt->value = def;
16843             opt->min = min;
16844             opt->max = max;
16845             opt->type = Spin; // Slider;
16846         } else if((p = strstr(opt->name, " -string "))) {
16847             opt->textValue = p+9;
16848             opt->type = TextBox;
16849         } else if((p = strstr(opt->name, " -file "))) {
16850             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16851             opt->textValue = p+7;
16852             opt->type = FileName; // FileName;
16853         } else if((p = strstr(opt->name, " -path "))) {
16854             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16855             opt->textValue = p+7;
16856             opt->type = PathName; // PathName;
16857         } else if(p = strstr(opt->name, " -check ")) {
16858             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16859             opt->value = (def != 0);
16860             opt->type = CheckBox;
16861         } else if(p = strstr(opt->name, " -combo ")) {
16862             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16863             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16864             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16865             opt->value = n = 0;
16866             while(q = StrStr(q, " /// ")) {
16867                 n++; *q = 0;    // count choices, and null-terminate each of them
16868                 q += 5;
16869                 if(*q == '*') { // remember default, which is marked with * prefix
16870                     q++;
16871                     opt->value = n;
16872                 }
16873                 cps->comboList[cps->comboCnt++] = q;
16874             }
16875             cps->comboList[cps->comboCnt++] = NULL;
16876             opt->max = n + 1;
16877             opt->type = ComboBox;
16878         } else if(p = strstr(opt->name, " -button")) {
16879             opt->type = Button;
16880         } else if(p = strstr(opt->name, " -save")) {
16881             opt->type = SaveButton;
16882         } else return FALSE;
16883         *p = 0; // terminate option name
16884         // now look if the command-line options define a setting for this engine option.
16885         if(cps->optionSettings && cps->optionSettings[0])
16886             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16887         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16888           snprintf(buf, MSG_SIZ, "option %s", p);
16889                 if(p = strstr(buf, ",")) *p = 0;
16890                 if(q = strchr(buf, '=')) switch(opt->type) {
16891                     case ComboBox:
16892                         for(n=0; n<opt->max; n++)
16893                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16894                         break;
16895                     case TextBox:
16896                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16897                         break;
16898                     case Spin:
16899                     case CheckBox:
16900                         opt->value = atoi(q+1);
16901                     default:
16902                         break;
16903                 }
16904                 strcat(buf, "\n");
16905                 SendToProgram(buf, cps);
16906         }
16907         return TRUE;
16908 }
16909
16910 void
16911 FeatureDone (ChessProgramState *cps, int val)
16912 {
16913   DelayedEventCallback cb = GetDelayedEvent();
16914   if ((cb == InitBackEnd3 && cps == &first) ||
16915       (cb == SettingsMenuIfReady && cps == &second) ||
16916       (cb == LoadEngine) ||
16917       (cb == TwoMachinesEventIfReady)) {
16918     CancelDelayedEvent();
16919     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16920   }
16921   cps->initDone = val;
16922   if(val) cps->reload = FALSE;
16923 }
16924
16925 /* Parse feature command from engine */
16926 void
16927 ParseFeatures (char *args, ChessProgramState *cps)
16928 {
16929   char *p = args;
16930   char *q = NULL;
16931   int val;
16932   char buf[MSG_SIZ];
16933
16934   for (;;) {
16935     while (*p == ' ') p++;
16936     if (*p == NULLCHAR) return;
16937
16938     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16939     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16940     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16941     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16942     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16943     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16944     if (BoolFeature(&p, "reuse", &val, cps)) {
16945       /* Engine can disable reuse, but can't enable it if user said no */
16946       if (!val) cps->reuse = FALSE;
16947       continue;
16948     }
16949     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16950     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16951       if (gameMode == TwoMachinesPlay) {
16952         DisplayTwoMachinesTitle();
16953       } else {
16954         DisplayTitle("");
16955       }
16956       continue;
16957     }
16958     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16959     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16960     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16961     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16962     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16963     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16964     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16965     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16966     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16967     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16968     if (IntFeature(&p, "done", &val, cps)) {
16969       FeatureDone(cps, val);
16970       continue;
16971     }
16972     /* Added by Tord: */
16973     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16974     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16975     /* End of additions by Tord */
16976
16977     /* [HGM] added features: */
16978     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16979     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16980     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16981     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16982     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16983     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16984     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16985     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16986         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16987         FREE(cps->option[cps->nrOptions].name);
16988         cps->option[cps->nrOptions].name = q; q = NULL;
16989         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16990           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16991             SendToProgram(buf, cps);
16992             continue;
16993         }
16994         if(cps->nrOptions >= MAX_OPTIONS) {
16995             cps->nrOptions--;
16996             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16997             DisplayError(buf, 0);
16998         }
16999         continue;
17000     }
17001     /* End of additions by HGM */
17002
17003     /* unknown feature: complain and skip */
17004     q = p;
17005     while (*q && *q != '=') q++;
17006     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17007     SendToProgram(buf, cps);
17008     p = q;
17009     if (*p == '=') {
17010       p++;
17011       if (*p == '\"') {
17012         p++;
17013         while (*p && *p != '\"') p++;
17014         if (*p == '\"') p++;
17015       } else {
17016         while (*p && *p != ' ') p++;
17017       }
17018     }
17019   }
17020
17021 }
17022
17023 void
17024 PeriodicUpdatesEvent (int newState)
17025 {
17026     if (newState == appData.periodicUpdates)
17027       return;
17028
17029     appData.periodicUpdates=newState;
17030
17031     /* Display type changes, so update it now */
17032 //    DisplayAnalysis();
17033
17034     /* Get the ball rolling again... */
17035     if (newState) {
17036         AnalysisPeriodicEvent(1);
17037         StartAnalysisClock();
17038     }
17039 }
17040
17041 void
17042 PonderNextMoveEvent (int newState)
17043 {
17044     if (newState == appData.ponderNextMove) return;
17045     if (gameMode == EditPosition) EditPositionDone(TRUE);
17046     if (newState) {
17047         SendToProgram("hard\n", &first);
17048         if (gameMode == TwoMachinesPlay) {
17049             SendToProgram("hard\n", &second);
17050         }
17051     } else {
17052         SendToProgram("easy\n", &first);
17053         thinkOutput[0] = NULLCHAR;
17054         if (gameMode == TwoMachinesPlay) {
17055             SendToProgram("easy\n", &second);
17056         }
17057     }
17058     appData.ponderNextMove = newState;
17059 }
17060
17061 void
17062 NewSettingEvent (int option, int *feature, char *command, int value)
17063 {
17064     char buf[MSG_SIZ];
17065
17066     if (gameMode == EditPosition) EditPositionDone(TRUE);
17067     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17068     if(feature == NULL || *feature) SendToProgram(buf, &first);
17069     if (gameMode == TwoMachinesPlay) {
17070         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17071     }
17072 }
17073
17074 void
17075 ShowThinkingEvent ()
17076 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17077 {
17078     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17079     int newState = appData.showThinking
17080         // [HGM] thinking: other features now need thinking output as well
17081         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17082
17083     if (oldState == newState) return;
17084     oldState = newState;
17085     if (gameMode == EditPosition) EditPositionDone(TRUE);
17086     if (oldState) {
17087         SendToProgram("post\n", &first);
17088         if (gameMode == TwoMachinesPlay) {
17089             SendToProgram("post\n", &second);
17090         }
17091     } else {
17092         SendToProgram("nopost\n", &first);
17093         thinkOutput[0] = NULLCHAR;
17094         if (gameMode == TwoMachinesPlay) {
17095             SendToProgram("nopost\n", &second);
17096         }
17097     }
17098 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17099 }
17100
17101 void
17102 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17103 {
17104   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17105   if (pr == NoProc) return;
17106   AskQuestion(title, question, replyPrefix, pr);
17107 }
17108
17109 void
17110 TypeInEvent (char firstChar)
17111 {
17112     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17113         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17114         gameMode == AnalyzeMode || gameMode == EditGame ||
17115         gameMode == EditPosition || gameMode == IcsExamining ||
17116         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17117         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17118                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17119                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17120         gameMode == Training) PopUpMoveDialog(firstChar);
17121 }
17122
17123 void
17124 TypeInDoneEvent (char *move)
17125 {
17126         Board board;
17127         int n, fromX, fromY, toX, toY;
17128         char promoChar;
17129         ChessMove moveType;
17130
17131         // [HGM] FENedit
17132         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17133                 EditPositionPasteFEN(move);
17134                 return;
17135         }
17136         // [HGM] movenum: allow move number to be typed in any mode
17137         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17138           ToNrEvent(2*n-1);
17139           return;
17140         }
17141         // undocumented kludge: allow command-line option to be typed in!
17142         // (potentially fatal, and does not implement the effect of the option.)
17143         // should only be used for options that are values on which future decisions will be made,
17144         // and definitely not on options that would be used during initialization.
17145         if(strstr(move, "!!! -") == move) {
17146             ParseArgsFromString(move+4);
17147             return;
17148         }
17149
17150       if (gameMode != EditGame && currentMove != forwardMostMove &&
17151         gameMode != Training) {
17152         DisplayMoveError(_("Displayed move is not current"));
17153       } else {
17154         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17155           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17156         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17157         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17158           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17159           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17160         } else {
17161           DisplayMoveError(_("Could not parse move"));
17162         }
17163       }
17164 }
17165
17166 void
17167 DisplayMove (int moveNumber)
17168 {
17169     char message[MSG_SIZ];
17170     char res[MSG_SIZ];
17171     char cpThinkOutput[MSG_SIZ];
17172
17173     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17174
17175     if (moveNumber == forwardMostMove - 1 ||
17176         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17177
17178         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17179
17180         if (strchr(cpThinkOutput, '\n')) {
17181             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17182         }
17183     } else {
17184         *cpThinkOutput = NULLCHAR;
17185     }
17186
17187     /* [AS] Hide thinking from human user */
17188     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17189         *cpThinkOutput = NULLCHAR;
17190         if( thinkOutput[0] != NULLCHAR ) {
17191             int i;
17192
17193             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17194                 cpThinkOutput[i] = '.';
17195             }
17196             cpThinkOutput[i] = NULLCHAR;
17197             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17198         }
17199     }
17200
17201     if (moveNumber == forwardMostMove - 1 &&
17202         gameInfo.resultDetails != NULL) {
17203         if (gameInfo.resultDetails[0] == NULLCHAR) {
17204           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17205         } else {
17206           snprintf(res, MSG_SIZ, " {%s} %s",
17207                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17208         }
17209     } else {
17210         res[0] = NULLCHAR;
17211     }
17212
17213     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17214         DisplayMessage(res, cpThinkOutput);
17215     } else {
17216       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17217                 WhiteOnMove(moveNumber) ? " " : ".. ",
17218                 parseList[moveNumber], res);
17219         DisplayMessage(message, cpThinkOutput);
17220     }
17221 }
17222
17223 void
17224 DisplayComment (int moveNumber, char *text)
17225 {
17226     char title[MSG_SIZ];
17227
17228     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17229       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17230     } else {
17231       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17232               WhiteOnMove(moveNumber) ? " " : ".. ",
17233               parseList[moveNumber]);
17234     }
17235     if (text != NULL && (appData.autoDisplayComment || commentUp))
17236         CommentPopUp(title, text);
17237 }
17238
17239 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17240  * might be busy thinking or pondering.  It can be omitted if your
17241  * gnuchess is configured to stop thinking immediately on any user
17242  * input.  However, that gnuchess feature depends on the FIONREAD
17243  * ioctl, which does not work properly on some flavors of Unix.
17244  */
17245 void
17246 Attention (ChessProgramState *cps)
17247 {
17248 #if ATTENTION
17249     if (!cps->useSigint) return;
17250     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17251     switch (gameMode) {
17252       case MachinePlaysWhite:
17253       case MachinePlaysBlack:
17254       case TwoMachinesPlay:
17255       case IcsPlayingWhite:
17256       case IcsPlayingBlack:
17257       case AnalyzeMode:
17258       case AnalyzeFile:
17259         /* Skip if we know it isn't thinking */
17260         if (!cps->maybeThinking) return;
17261         if (appData.debugMode)
17262           fprintf(debugFP, "Interrupting %s\n", cps->which);
17263         InterruptChildProcess(cps->pr);
17264         cps->maybeThinking = FALSE;
17265         break;
17266       default:
17267         break;
17268     }
17269 #endif /*ATTENTION*/
17270 }
17271
17272 int
17273 CheckFlags ()
17274 {
17275     if (whiteTimeRemaining <= 0) {
17276         if (!whiteFlag) {
17277             whiteFlag = TRUE;
17278             if (appData.icsActive) {
17279                 if (appData.autoCallFlag &&
17280                     gameMode == IcsPlayingBlack && !blackFlag) {
17281                   SendToICS(ics_prefix);
17282                   SendToICS("flag\n");
17283                 }
17284             } else {
17285                 if (blackFlag) {
17286                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17287                 } else {
17288                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17289                     if (appData.autoCallFlag) {
17290                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17291                         return TRUE;
17292                     }
17293                 }
17294             }
17295         }
17296     }
17297     if (blackTimeRemaining <= 0) {
17298         if (!blackFlag) {
17299             blackFlag = TRUE;
17300             if (appData.icsActive) {
17301                 if (appData.autoCallFlag &&
17302                     gameMode == IcsPlayingWhite && !whiteFlag) {
17303                   SendToICS(ics_prefix);
17304                   SendToICS("flag\n");
17305                 }
17306             } else {
17307                 if (whiteFlag) {
17308                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17309                 } else {
17310                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17311                     if (appData.autoCallFlag) {
17312                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17313                         return TRUE;
17314                     }
17315                 }
17316             }
17317         }
17318     }
17319     return FALSE;
17320 }
17321
17322 void
17323 CheckTimeControl ()
17324 {
17325     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17326         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17327
17328     /*
17329      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17330      */
17331     if ( !WhiteOnMove(forwardMostMove) ) {
17332         /* White made time control */
17333         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17334         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17335         /* [HGM] time odds: correct new time quota for time odds! */
17336                                             / WhitePlayer()->timeOdds;
17337         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17338     } else {
17339         lastBlack -= blackTimeRemaining;
17340         /* Black made time control */
17341         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17342                                             / WhitePlayer()->other->timeOdds;
17343         lastWhite = whiteTimeRemaining;
17344     }
17345 }
17346
17347 void
17348 DisplayBothClocks ()
17349 {
17350     int wom = gameMode == EditPosition ?
17351       !blackPlaysFirst : WhiteOnMove(currentMove);
17352     DisplayWhiteClock(whiteTimeRemaining, wom);
17353     DisplayBlackClock(blackTimeRemaining, !wom);
17354 }
17355
17356
17357 /* Timekeeping seems to be a portability nightmare.  I think everyone
17358    has ftime(), but I'm really not sure, so I'm including some ifdefs
17359    to use other calls if you don't.  Clocks will be less accurate if
17360    you have neither ftime nor gettimeofday.
17361 */
17362
17363 /* VS 2008 requires the #include outside of the function */
17364 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17365 #include <sys/timeb.h>
17366 #endif
17367
17368 /* Get the current time as a TimeMark */
17369 void
17370 GetTimeMark (TimeMark *tm)
17371 {
17372 #if HAVE_GETTIMEOFDAY
17373
17374     struct timeval timeVal;
17375     struct timezone timeZone;
17376
17377     gettimeofday(&timeVal, &timeZone);
17378     tm->sec = (long) timeVal.tv_sec;
17379     tm->ms = (int) (timeVal.tv_usec / 1000L);
17380
17381 #else /*!HAVE_GETTIMEOFDAY*/
17382 #if HAVE_FTIME
17383
17384 // include <sys/timeb.h> / moved to just above start of function
17385     struct timeb timeB;
17386
17387     ftime(&timeB);
17388     tm->sec = (long) timeB.time;
17389     tm->ms = (int) timeB.millitm;
17390
17391 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17392     tm->sec = (long) time(NULL);
17393     tm->ms = 0;
17394 #endif
17395 #endif
17396 }
17397
17398 /* Return the difference in milliseconds between two
17399    time marks.  We assume the difference will fit in a long!
17400 */
17401 long
17402 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17403 {
17404     return 1000L*(tm2->sec - tm1->sec) +
17405            (long) (tm2->ms - tm1->ms);
17406 }
17407
17408
17409 /*
17410  * Code to manage the game clocks.
17411  *
17412  * In tournament play, black starts the clock and then white makes a move.
17413  * We give the human user a slight advantage if he is playing white---the
17414  * clocks don't run until he makes his first move, so it takes zero time.
17415  * Also, we don't account for network lag, so we could get out of sync
17416  * with GNU Chess's clock -- but then, referees are always right.
17417  */
17418
17419 static TimeMark tickStartTM;
17420 static long intendedTickLength;
17421
17422 long
17423 NextTickLength (long timeRemaining)
17424 {
17425     long nominalTickLength, nextTickLength;
17426
17427     if (timeRemaining > 0L && timeRemaining <= 10000L)
17428       nominalTickLength = 100L;
17429     else
17430       nominalTickLength = 1000L;
17431     nextTickLength = timeRemaining % nominalTickLength;
17432     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17433
17434     return nextTickLength;
17435 }
17436
17437 /* Adjust clock one minute up or down */
17438 void
17439 AdjustClock (Boolean which, int dir)
17440 {
17441     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17442     if(which) blackTimeRemaining += 60000*dir;
17443     else      whiteTimeRemaining += 60000*dir;
17444     DisplayBothClocks();
17445     adjustedClock = TRUE;
17446 }
17447
17448 /* Stop clocks and reset to a fresh time control */
17449 void
17450 ResetClocks ()
17451 {
17452     (void) StopClockTimer();
17453     if (appData.icsActive) {
17454         whiteTimeRemaining = blackTimeRemaining = 0;
17455     } else if (searchTime) {
17456         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17457         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17458     } else { /* [HGM] correct new time quote for time odds */
17459         whiteTC = blackTC = fullTimeControlString;
17460         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17461         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17462     }
17463     if (whiteFlag || blackFlag) {
17464         DisplayTitle("");
17465         whiteFlag = blackFlag = FALSE;
17466     }
17467     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17468     DisplayBothClocks();
17469     adjustedClock = FALSE;
17470 }
17471
17472 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17473
17474 /* Decrement running clock by amount of time that has passed */
17475 void
17476 DecrementClocks ()
17477 {
17478     long timeRemaining;
17479     long lastTickLength, fudge;
17480     TimeMark now;
17481
17482     if (!appData.clockMode) return;
17483     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17484
17485     GetTimeMark(&now);
17486
17487     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17488
17489     /* Fudge if we woke up a little too soon */
17490     fudge = intendedTickLength - lastTickLength;
17491     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17492
17493     if (WhiteOnMove(forwardMostMove)) {
17494         if(whiteNPS >= 0) lastTickLength = 0;
17495         timeRemaining = whiteTimeRemaining -= lastTickLength;
17496         if(timeRemaining < 0 && !appData.icsActive) {
17497             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17498             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17499                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17500                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17501             }
17502         }
17503         DisplayWhiteClock(whiteTimeRemaining - fudge,
17504                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17505     } else {
17506         if(blackNPS >= 0) lastTickLength = 0;
17507         timeRemaining = blackTimeRemaining -= lastTickLength;
17508         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17509             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17510             if(suddenDeath) {
17511                 blackStartMove = forwardMostMove;
17512                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17513             }
17514         }
17515         DisplayBlackClock(blackTimeRemaining - fudge,
17516                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17517     }
17518     if (CheckFlags()) return;
17519
17520     if(twoBoards) { // count down secondary board's clocks as well
17521         activePartnerTime -= lastTickLength;
17522         partnerUp = 1;
17523         if(activePartner == 'W')
17524             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17525         else
17526             DisplayBlackClock(activePartnerTime, TRUE);
17527         partnerUp = 0;
17528     }
17529
17530     tickStartTM = now;
17531     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17532     StartClockTimer(intendedTickLength);
17533
17534     /* if the time remaining has fallen below the alarm threshold, sound the
17535      * alarm. if the alarm has sounded and (due to a takeback or time control
17536      * with increment) the time remaining has increased to a level above the
17537      * threshold, reset the alarm so it can sound again.
17538      */
17539
17540     if (appData.icsActive && appData.icsAlarm) {
17541
17542         /* make sure we are dealing with the user's clock */
17543         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17544                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17545            )) return;
17546
17547         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17548             alarmSounded = FALSE;
17549         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17550             PlayAlarmSound();
17551             alarmSounded = TRUE;
17552         }
17553     }
17554 }
17555
17556
17557 /* A player has just moved, so stop the previously running
17558    clock and (if in clock mode) start the other one.
17559    We redisplay both clocks in case we're in ICS mode, because
17560    ICS gives us an update to both clocks after every move.
17561    Note that this routine is called *after* forwardMostMove
17562    is updated, so the last fractional tick must be subtracted
17563    from the color that is *not* on move now.
17564 */
17565 void
17566 SwitchClocks (int newMoveNr)
17567 {
17568     long lastTickLength;
17569     TimeMark now;
17570     int flagged = FALSE;
17571
17572     GetTimeMark(&now);
17573
17574     if (StopClockTimer() && appData.clockMode) {
17575         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17576         if (!WhiteOnMove(forwardMostMove)) {
17577             if(blackNPS >= 0) lastTickLength = 0;
17578             blackTimeRemaining -= lastTickLength;
17579            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17580 //         if(pvInfoList[forwardMostMove].time == -1)
17581                  pvInfoList[forwardMostMove].time =               // use GUI time
17582                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17583         } else {
17584            if(whiteNPS >= 0) lastTickLength = 0;
17585            whiteTimeRemaining -= lastTickLength;
17586            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17587 //         if(pvInfoList[forwardMostMove].time == -1)
17588                  pvInfoList[forwardMostMove].time =
17589                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17590         }
17591         flagged = CheckFlags();
17592     }
17593     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17594     CheckTimeControl();
17595
17596     if (flagged || !appData.clockMode) return;
17597
17598     switch (gameMode) {
17599       case MachinePlaysBlack:
17600       case MachinePlaysWhite:
17601       case BeginningOfGame:
17602         if (pausing) return;
17603         break;
17604
17605       case EditGame:
17606       case PlayFromGameFile:
17607       case IcsExamining:
17608         return;
17609
17610       default:
17611         break;
17612     }
17613
17614     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17615         if(WhiteOnMove(forwardMostMove))
17616              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17617         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17618     }
17619
17620     tickStartTM = now;
17621     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17622       whiteTimeRemaining : blackTimeRemaining);
17623     StartClockTimer(intendedTickLength);
17624 }
17625
17626
17627 /* Stop both clocks */
17628 void
17629 StopClocks ()
17630 {
17631     long lastTickLength;
17632     TimeMark now;
17633
17634     if (!StopClockTimer()) return;
17635     if (!appData.clockMode) return;
17636
17637     GetTimeMark(&now);
17638
17639     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17640     if (WhiteOnMove(forwardMostMove)) {
17641         if(whiteNPS >= 0) lastTickLength = 0;
17642         whiteTimeRemaining -= lastTickLength;
17643         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17644     } else {
17645         if(blackNPS >= 0) lastTickLength = 0;
17646         blackTimeRemaining -= lastTickLength;
17647         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17648     }
17649     CheckFlags();
17650 }
17651
17652 /* Start clock of player on move.  Time may have been reset, so
17653    if clock is already running, stop and restart it. */
17654 void
17655 StartClocks ()
17656 {
17657     (void) StopClockTimer(); /* in case it was running already */
17658     DisplayBothClocks();
17659     if (CheckFlags()) return;
17660
17661     if (!appData.clockMode) return;
17662     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17663
17664     GetTimeMark(&tickStartTM);
17665     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17666       whiteTimeRemaining : blackTimeRemaining);
17667
17668    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17669     whiteNPS = blackNPS = -1;
17670     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17671        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17672         whiteNPS = first.nps;
17673     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17674        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17675         blackNPS = first.nps;
17676     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17677         whiteNPS = second.nps;
17678     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17679         blackNPS = second.nps;
17680     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17681
17682     StartClockTimer(intendedTickLength);
17683 }
17684
17685 char *
17686 TimeString (long ms)
17687 {
17688     long second, minute, hour, day;
17689     char *sign = "";
17690     static char buf[32];
17691
17692     if (ms > 0 && ms <= 9900) {
17693       /* convert milliseconds to tenths, rounding up */
17694       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17695
17696       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17697       return buf;
17698     }
17699
17700     /* convert milliseconds to seconds, rounding up */
17701     /* use floating point to avoid strangeness of integer division
17702        with negative dividends on many machines */
17703     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17704
17705     if (second < 0) {
17706         sign = "-";
17707         second = -second;
17708     }
17709
17710     day = second / (60 * 60 * 24);
17711     second = second % (60 * 60 * 24);
17712     hour = second / (60 * 60);
17713     second = second % (60 * 60);
17714     minute = second / 60;
17715     second = second % 60;
17716
17717     if (day > 0)
17718       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17719               sign, day, hour, minute, second);
17720     else if (hour > 0)
17721       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17722     else
17723       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17724
17725     return buf;
17726 }
17727
17728
17729 /*
17730  * This is necessary because some C libraries aren't ANSI C compliant yet.
17731  */
17732 char *
17733 StrStr (char *string, char *match)
17734 {
17735     int i, length;
17736
17737     length = strlen(match);
17738
17739     for (i = strlen(string) - length; i >= 0; i--, string++)
17740       if (!strncmp(match, string, length))
17741         return string;
17742
17743     return NULL;
17744 }
17745
17746 char *
17747 StrCaseStr (char *string, char *match)
17748 {
17749     int i, j, length;
17750
17751     length = strlen(match);
17752
17753     for (i = strlen(string) - length; i >= 0; i--, string++) {
17754         for (j = 0; j < length; j++) {
17755             if (ToLower(match[j]) != ToLower(string[j]))
17756               break;
17757         }
17758         if (j == length) return string;
17759     }
17760
17761     return NULL;
17762 }
17763
17764 #ifndef _amigados
17765 int
17766 StrCaseCmp (char *s1, char *s2)
17767 {
17768     char c1, c2;
17769
17770     for (;;) {
17771         c1 = ToLower(*s1++);
17772         c2 = ToLower(*s2++);
17773         if (c1 > c2) return 1;
17774         if (c1 < c2) return -1;
17775         if (c1 == NULLCHAR) return 0;
17776     }
17777 }
17778
17779
17780 int
17781 ToLower (int c)
17782 {
17783     return isupper(c) ? tolower(c) : c;
17784 }
17785
17786
17787 int
17788 ToUpper (int c)
17789 {
17790     return islower(c) ? toupper(c) : c;
17791 }
17792 #endif /* !_amigados    */
17793
17794 char *
17795 StrSave (char *s)
17796 {
17797   char *ret;
17798
17799   if ((ret = (char *) malloc(strlen(s) + 1)))
17800     {
17801       safeStrCpy(ret, s, strlen(s)+1);
17802     }
17803   return ret;
17804 }
17805
17806 char *
17807 StrSavePtr (char *s, char **savePtr)
17808 {
17809     if (*savePtr) {
17810         free(*savePtr);
17811     }
17812     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17813       safeStrCpy(*savePtr, s, strlen(s)+1);
17814     }
17815     return(*savePtr);
17816 }
17817
17818 char *
17819 PGNDate ()
17820 {
17821     time_t clock;
17822     struct tm *tm;
17823     char buf[MSG_SIZ];
17824
17825     clock = time((time_t *)NULL);
17826     tm = localtime(&clock);
17827     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17828             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17829     return StrSave(buf);
17830 }
17831
17832
17833 char *
17834 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17835 {
17836     int i, j, fromX, fromY, toX, toY;
17837     int whiteToPlay;
17838     char buf[MSG_SIZ];
17839     char *p, *q;
17840     int emptycount;
17841     ChessSquare piece;
17842
17843     whiteToPlay = (gameMode == EditPosition) ?
17844       !blackPlaysFirst : (move % 2 == 0);
17845     p = buf;
17846
17847     /* Piece placement data */
17848     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17849         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17850         emptycount = 0;
17851         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17852             if (boards[move][i][j] == EmptySquare) {
17853                 emptycount++;
17854             } else { ChessSquare piece = boards[move][i][j];
17855                 if (emptycount > 0) {
17856                     if(emptycount<10) /* [HGM] can be >= 10 */
17857                         *p++ = '0' + emptycount;
17858                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17859                     emptycount = 0;
17860                 }
17861                 if(PieceToChar(piece) == '+') {
17862                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17863                     *p++ = '+';
17864                     piece = (ChessSquare)(CHUDEMOTED piece);
17865                 }
17866                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17867                 if(p[-1] == '~') {
17868                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17869                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17870                     *p++ = '~';
17871                 }
17872             }
17873         }
17874         if (emptycount > 0) {
17875             if(emptycount<10) /* [HGM] can be >= 10 */
17876                 *p++ = '0' + emptycount;
17877             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17878             emptycount = 0;
17879         }
17880         *p++ = '/';
17881     }
17882     *(p - 1) = ' ';
17883
17884     /* [HGM] print Crazyhouse or Shogi holdings */
17885     if( gameInfo.holdingsWidth ) {
17886         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17887         q = p;
17888         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17889             piece = boards[move][i][BOARD_WIDTH-1];
17890             if( piece != EmptySquare )
17891               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17892                   *p++ = PieceToChar(piece);
17893         }
17894         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17895             piece = boards[move][BOARD_HEIGHT-i-1][0];
17896             if( piece != EmptySquare )
17897               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17898                   *p++ = PieceToChar(piece);
17899         }
17900
17901         if( q == p ) *p++ = '-';
17902         *p++ = ']';
17903         *p++ = ' ';
17904     }
17905
17906     /* Active color */
17907     *p++ = whiteToPlay ? 'w' : 'b';
17908     *p++ = ' ';
17909
17910   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17911     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17912   } else {
17913   if(nrCastlingRights) {
17914      int handW=0, handB=0;
17915      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17916         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17917         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17918      }
17919      q = p;
17920      if(appData.fischerCastling) {
17921         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17922            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17923                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17924         } else {
17925        /* [HGM] write directly from rights */
17926            if(boards[move][CASTLING][2] != NoRights &&
17927               boards[move][CASTLING][0] != NoRights   )
17928                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17929            if(boards[move][CASTLING][2] != NoRights &&
17930               boards[move][CASTLING][1] != NoRights   )
17931                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17932         }
17933         if(handB) {
17934            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17935                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17936         } else {
17937            if(boards[move][CASTLING][5] != NoRights &&
17938               boards[move][CASTLING][3] != NoRights   )
17939                 *p++ = boards[move][CASTLING][3] + AAA;
17940            if(boards[move][CASTLING][5] != NoRights &&
17941               boards[move][CASTLING][4] != NoRights   )
17942                 *p++ = boards[move][CASTLING][4] + AAA;
17943         }
17944      } else {
17945
17946         /* [HGM] write true castling rights */
17947         if( nrCastlingRights == 6 ) {
17948             int q, k=0;
17949             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17950                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17951             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17952                  boards[move][CASTLING][2] != NoRights  );
17953             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17954                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17955                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17956                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17957             }
17958             if(q) *p++ = 'Q';
17959             k = 0;
17960             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17961                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17962             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17963                  boards[move][CASTLING][5] != NoRights  );
17964             if(handB) {
17965                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17966                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17967                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17968             }
17969             if(q) *p++ = 'q';
17970         }
17971      }
17972      if (q == p) *p++ = '-'; /* No castling rights */
17973      *p++ = ' ';
17974   }
17975
17976   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17977      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17978      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17979     /* En passant target square */
17980     if (move > backwardMostMove) {
17981         fromX = moveList[move - 1][0] - AAA;
17982         fromY = moveList[move - 1][1] - ONE;
17983         toX = moveList[move - 1][2] - AAA;
17984         toY = moveList[move - 1][3] - ONE;
17985         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17986             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17987             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17988             fromX == toX) {
17989             /* 2-square pawn move just happened */
17990             *p++ = toX + AAA;
17991             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17992         } else {
17993             *p++ = '-';
17994         }
17995     } else if(move == backwardMostMove) {
17996         // [HGM] perhaps we should always do it like this, and forget the above?
17997         if((signed char)boards[move][EP_STATUS] >= 0) {
17998             *p++ = boards[move][EP_STATUS] + AAA;
17999             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18000         } else {
18001             *p++ = '-';
18002         }
18003     } else {
18004         *p++ = '-';
18005     }
18006     *p++ = ' ';
18007   }
18008   }
18009
18010     if(moveCounts)
18011     {   int i = 0, j=move;
18012
18013         /* [HGM] find reversible plies */
18014         if (appData.debugMode) { int k;
18015             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18016             for(k=backwardMostMove; k<=forwardMostMove; k++)
18017                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18018
18019         }
18020
18021         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18022         if( j == backwardMostMove ) i += initialRulePlies;
18023         sprintf(p, "%d ", i);
18024         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18025
18026         /* Fullmove number */
18027         sprintf(p, "%d", (move / 2) + 1);
18028     } else *--p = NULLCHAR;
18029
18030     return StrSave(buf);
18031 }
18032
18033 Boolean
18034 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18035 {
18036     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18037     char *p, c;
18038     int emptycount, virgin[BOARD_FILES];
18039     ChessSquare piece;
18040
18041     p = fen;
18042
18043     /* Piece placement data */
18044     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18045         j = 0;
18046         for (;;) {
18047             if (*p == '/' || *p == ' ' || *p == '[' ) {
18048                 if(j > w) w = j;
18049                 emptycount = gameInfo.boardWidth - j;
18050                 while (emptycount--)
18051                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18052                 if (*p == '/') p++;
18053                 else if(autoSize) { // we stumbled unexpectedly into end of board
18054                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18055                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18056                     }
18057                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18058                 }
18059                 break;
18060 #if(BOARD_FILES >= 10)*0
18061             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18062                 p++; emptycount=10;
18063                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18064                 while (emptycount--)
18065                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18066 #endif
18067             } else if (*p == '*') {
18068                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18069             } else if (isdigit(*p)) {
18070                 emptycount = *p++ - '0';
18071                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18072                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18073                 while (emptycount--)
18074                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18075             } else if (*p == '<') {
18076                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18077                 else if (i != 0 || !shuffle) return FALSE;
18078                 p++;
18079             } else if (shuffle && *p == '>') {
18080                 p++; // for now ignore closing shuffle range, and assume rank-end
18081             } else if (*p == '?') {
18082                 if (j >= gameInfo.boardWidth) return FALSE;
18083                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18084                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18085             } else if (*p == '+' || isalpha(*p)) {
18086                 if (j >= gameInfo.boardWidth) return FALSE;
18087                 if(*p=='+') {
18088                     piece = CharToPiece(*++p);
18089                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18090                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18091                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18092                 } else piece = CharToPiece(*p++);
18093
18094                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18095                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18096                     piece = (ChessSquare) (PROMOTED piece);
18097                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18098                     p++;
18099                 }
18100                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18101                 if(piece == WhiteKing) wKingRank = i;
18102                 if(piece == BlackKing) bKingRank = i;
18103             } else {
18104                 return FALSE;
18105             }
18106         }
18107     }
18108     while (*p == '/' || *p == ' ') p++;
18109
18110     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18111
18112     /* [HGM] by default clear Crazyhouse holdings, if present */
18113     if(gameInfo.holdingsWidth) {
18114        for(i=0; i<BOARD_HEIGHT; i++) {
18115            board[i][0]             = EmptySquare; /* black holdings */
18116            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18117            board[i][1]             = (ChessSquare) 0; /* black counts */
18118            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18119        }
18120     }
18121
18122     /* [HGM] look for Crazyhouse holdings here */
18123     while(*p==' ') p++;
18124     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18125         int swap=0, wcnt=0, bcnt=0;
18126         if(*p == '[') p++;
18127         if(*p == '<') swap++, p++;
18128         if(*p == '-' ) p++; /* empty holdings */ else {
18129             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18130             /* if we would allow FEN reading to set board size, we would   */
18131             /* have to add holdings and shift the board read so far here   */
18132             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18133                 p++;
18134                 if((int) piece >= (int) BlackPawn ) {
18135                     i = (int)piece - (int)BlackPawn;
18136                     i = PieceToNumber((ChessSquare)i);
18137                     if( i >= gameInfo.holdingsSize ) return FALSE;
18138                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18139                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18140                     bcnt++;
18141                 } else {
18142                     i = (int)piece - (int)WhitePawn;
18143                     i = PieceToNumber((ChessSquare)i);
18144                     if( i >= gameInfo.holdingsSize ) return FALSE;
18145                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18146                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18147                     wcnt++;
18148                 }
18149             }
18150             if(subst) { // substitute back-rank question marks by holdings pieces
18151                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18152                     int k, m, n = bcnt + 1;
18153                     if(board[0][j] == ClearBoard) {
18154                         if(!wcnt) return FALSE;
18155                         n = rand() % wcnt;
18156                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18157                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18158                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18159                             break;
18160                         }
18161                     }
18162                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18163                         if(!bcnt) return FALSE;
18164                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18165                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18166                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18167                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18168                             break;
18169                         }
18170                     }
18171                 }
18172                 subst = 0;
18173             }
18174         }
18175         if(*p == ']') p++;
18176     }
18177
18178     if(subst) return FALSE; // substitution requested, but no holdings
18179
18180     while(*p == ' ') p++;
18181
18182     /* Active color */
18183     c = *p++;
18184     if(appData.colorNickNames) {
18185       if( c == appData.colorNickNames[0] ) c = 'w'; else
18186       if( c == appData.colorNickNames[1] ) c = 'b';
18187     }
18188     switch (c) {
18189       case 'w':
18190         *blackPlaysFirst = FALSE;
18191         break;
18192       case 'b':
18193         *blackPlaysFirst = TRUE;
18194         break;
18195       default:
18196         return FALSE;
18197     }
18198
18199     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18200     /* return the extra info in global variiables             */
18201
18202     while(*p==' ') p++;
18203
18204     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18205         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18206         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18207     }
18208
18209     /* set defaults in case FEN is incomplete */
18210     board[EP_STATUS] = EP_UNKNOWN;
18211     for(i=0; i<nrCastlingRights; i++ ) {
18212         board[CASTLING][i] =
18213             appData.fischerCastling ? NoRights : initialRights[i];
18214     }   /* assume possible unless obviously impossible */
18215     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18216     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18217     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18218                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18219     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18220     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18221     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18222                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18223     FENrulePlies = 0;
18224
18225     if(nrCastlingRights) {
18226       int fischer = 0;
18227       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18228       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18229           /* castling indicator present, so default becomes no castlings */
18230           for(i=0; i<nrCastlingRights; i++ ) {
18231                  board[CASTLING][i] = NoRights;
18232           }
18233       }
18234       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18235              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18236              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18237              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18238         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18239
18240         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18241             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18242             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18243         }
18244         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18245             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18246         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18247                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18248         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18249                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18250         switch(c) {
18251           case'K':
18252               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18253               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18254               board[CASTLING][2] = whiteKingFile;
18255               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18256               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18257               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18258               break;
18259           case'Q':
18260               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18261               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18262               board[CASTLING][2] = whiteKingFile;
18263               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18264               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18265               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18266               break;
18267           case'k':
18268               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18269               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18270               board[CASTLING][5] = blackKingFile;
18271               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18272               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18273               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18274               break;
18275           case'q':
18276               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18277               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18278               board[CASTLING][5] = blackKingFile;
18279               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18280               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18281               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18282           case '-':
18283               break;
18284           default: /* FRC castlings */
18285               if(c >= 'a') { /* black rights */
18286                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18287                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18288                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18289                   if(i == BOARD_RGHT) break;
18290                   board[CASTLING][5] = i;
18291                   c -= AAA;
18292                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18293                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18294                   if(c > i)
18295                       board[CASTLING][3] = c;
18296                   else
18297                       board[CASTLING][4] = c;
18298               } else { /* white rights */
18299                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18300                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18301                     if(board[0][i] == WhiteKing) break;
18302                   if(i == BOARD_RGHT) break;
18303                   board[CASTLING][2] = i;
18304                   c -= AAA - 'a' + 'A';
18305                   if(board[0][c] >= WhiteKing) break;
18306                   if(c > i)
18307                       board[CASTLING][0] = c;
18308                   else
18309                       board[CASTLING][1] = c;
18310               }
18311         }
18312       }
18313       for(i=0; i<nrCastlingRights; i++)
18314         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18315       if(gameInfo.variant == VariantSChess)
18316         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18317       if(fischer && shuffle) appData.fischerCastling = TRUE;
18318     if (appData.debugMode) {
18319         fprintf(debugFP, "FEN castling rights:");
18320         for(i=0; i<nrCastlingRights; i++)
18321         fprintf(debugFP, " %d", board[CASTLING][i]);
18322         fprintf(debugFP, "\n");
18323     }
18324
18325       while(*p==' ') p++;
18326     }
18327
18328     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18329
18330     /* read e.p. field in games that know e.p. capture */
18331     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18332        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18333        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18334       if(*p=='-') {
18335         p++; board[EP_STATUS] = EP_NONE;
18336       } else {
18337          char c = *p++ - AAA;
18338
18339          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18340          if(*p >= '0' && *p <='9') p++;
18341          board[EP_STATUS] = c;
18342       }
18343     }
18344
18345
18346     if(sscanf(p, "%d", &i) == 1) {
18347         FENrulePlies = i; /* 50-move ply counter */
18348         /* (The move number is still ignored)    */
18349     }
18350
18351     return TRUE;
18352 }
18353
18354 void
18355 EditPositionPasteFEN (char *fen)
18356 {
18357   if (fen != NULL) {
18358     Board initial_position;
18359
18360     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18361       DisplayError(_("Bad FEN position in clipboard"), 0);
18362       return ;
18363     } else {
18364       int savedBlackPlaysFirst = blackPlaysFirst;
18365       EditPositionEvent();
18366       blackPlaysFirst = savedBlackPlaysFirst;
18367       CopyBoard(boards[0], initial_position);
18368       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18369       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18370       DisplayBothClocks();
18371       DrawPosition(FALSE, boards[currentMove]);
18372     }
18373   }
18374 }
18375
18376 static char cseq[12] = "\\   ";
18377
18378 Boolean
18379 set_cont_sequence (char *new_seq)
18380 {
18381     int len;
18382     Boolean ret;
18383
18384     // handle bad attempts to set the sequence
18385         if (!new_seq)
18386                 return 0; // acceptable error - no debug
18387
18388     len = strlen(new_seq);
18389     ret = (len > 0) && (len < sizeof(cseq));
18390     if (ret)
18391       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18392     else if (appData.debugMode)
18393       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18394     return ret;
18395 }
18396
18397 /*
18398     reformat a source message so words don't cross the width boundary.  internal
18399     newlines are not removed.  returns the wrapped size (no null character unless
18400     included in source message).  If dest is NULL, only calculate the size required
18401     for the dest buffer.  lp argument indicats line position upon entry, and it's
18402     passed back upon exit.
18403 */
18404 int
18405 wrap (char *dest, char *src, int count, int width, int *lp)
18406 {
18407     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18408
18409     cseq_len = strlen(cseq);
18410     old_line = line = *lp;
18411     ansi = len = clen = 0;
18412
18413     for (i=0; i < count; i++)
18414     {
18415         if (src[i] == '\033')
18416             ansi = 1;
18417
18418         // if we hit the width, back up
18419         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18420         {
18421             // store i & len in case the word is too long
18422             old_i = i, old_len = len;
18423
18424             // find the end of the last word
18425             while (i && src[i] != ' ' && src[i] != '\n')
18426             {
18427                 i--;
18428                 len--;
18429             }
18430
18431             // word too long?  restore i & len before splitting it
18432             if ((old_i-i+clen) >= width)
18433             {
18434                 i = old_i;
18435                 len = old_len;
18436             }
18437
18438             // extra space?
18439             if (i && src[i-1] == ' ')
18440                 len--;
18441
18442             if (src[i] != ' ' && src[i] != '\n')
18443             {
18444                 i--;
18445                 if (len)
18446                     len--;
18447             }
18448
18449             // now append the newline and continuation sequence
18450             if (dest)
18451                 dest[len] = '\n';
18452             len++;
18453             if (dest)
18454                 strncpy(dest+len, cseq, cseq_len);
18455             len += cseq_len;
18456             line = cseq_len;
18457             clen = cseq_len;
18458             continue;
18459         }
18460
18461         if (dest)
18462             dest[len] = src[i];
18463         len++;
18464         if (!ansi)
18465             line++;
18466         if (src[i] == '\n')
18467             line = 0;
18468         if (src[i] == 'm')
18469             ansi = 0;
18470     }
18471     if (dest && appData.debugMode)
18472     {
18473         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18474             count, width, line, len, *lp);
18475         show_bytes(debugFP, src, count);
18476         fprintf(debugFP, "\ndest: ");
18477         show_bytes(debugFP, dest, len);
18478         fprintf(debugFP, "\n");
18479     }
18480     *lp = dest ? line : old_line;
18481
18482     return len;
18483 }
18484
18485 // [HGM] vari: routines for shelving variations
18486 Boolean modeRestore = FALSE;
18487
18488 void
18489 PushInner (int firstMove, int lastMove)
18490 {
18491         int i, j, nrMoves = lastMove - firstMove;
18492
18493         // push current tail of game on stack
18494         savedResult[storedGames] = gameInfo.result;
18495         savedDetails[storedGames] = gameInfo.resultDetails;
18496         gameInfo.resultDetails = NULL;
18497         savedFirst[storedGames] = firstMove;
18498         savedLast [storedGames] = lastMove;
18499         savedFramePtr[storedGames] = framePtr;
18500         framePtr -= nrMoves; // reserve space for the boards
18501         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18502             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18503             for(j=0; j<MOVE_LEN; j++)
18504                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18505             for(j=0; j<2*MOVE_LEN; j++)
18506                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18507             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18508             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18509             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18510             pvInfoList[firstMove+i-1].depth = 0;
18511             commentList[framePtr+i] = commentList[firstMove+i];
18512             commentList[firstMove+i] = NULL;
18513         }
18514
18515         storedGames++;
18516         forwardMostMove = firstMove; // truncate game so we can start variation
18517 }
18518
18519 void
18520 PushTail (int firstMove, int lastMove)
18521 {
18522         if(appData.icsActive) { // only in local mode
18523                 forwardMostMove = currentMove; // mimic old ICS behavior
18524                 return;
18525         }
18526         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18527
18528         PushInner(firstMove, lastMove);
18529         if(storedGames == 1) GreyRevert(FALSE);
18530         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18531 }
18532
18533 void
18534 PopInner (Boolean annotate)
18535 {
18536         int i, j, nrMoves;
18537         char buf[8000], moveBuf[20];
18538
18539         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18540         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18541         nrMoves = savedLast[storedGames] - currentMove;
18542         if(annotate) {
18543                 int cnt = 10;
18544                 if(!WhiteOnMove(currentMove))
18545                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18546                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18547                 for(i=currentMove; i<forwardMostMove; i++) {
18548                         if(WhiteOnMove(i))
18549                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18550                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18551                         strcat(buf, moveBuf);
18552                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18553                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18554                 }
18555                 strcat(buf, ")");
18556         }
18557         for(i=1; i<=nrMoves; i++) { // copy last variation back
18558             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18559             for(j=0; j<MOVE_LEN; j++)
18560                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18561             for(j=0; j<2*MOVE_LEN; j++)
18562                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18563             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18564             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18565             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18566             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18567             commentList[currentMove+i] = commentList[framePtr+i];
18568             commentList[framePtr+i] = NULL;
18569         }
18570         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18571         framePtr = savedFramePtr[storedGames];
18572         gameInfo.result = savedResult[storedGames];
18573         if(gameInfo.resultDetails != NULL) {
18574             free(gameInfo.resultDetails);
18575       }
18576         gameInfo.resultDetails = savedDetails[storedGames];
18577         forwardMostMove = currentMove + nrMoves;
18578 }
18579
18580 Boolean
18581 PopTail (Boolean annotate)
18582 {
18583         if(appData.icsActive) return FALSE; // only in local mode
18584         if(!storedGames) return FALSE; // sanity
18585         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18586
18587         PopInner(annotate);
18588         if(currentMove < forwardMostMove) ForwardEvent(); else
18589         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18590
18591         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18592         return TRUE;
18593 }
18594
18595 void
18596 CleanupTail ()
18597 {       // remove all shelved variations
18598         int i;
18599         for(i=0; i<storedGames; i++) {
18600             if(savedDetails[i])
18601                 free(savedDetails[i]);
18602             savedDetails[i] = NULL;
18603         }
18604         for(i=framePtr; i<MAX_MOVES; i++) {
18605                 if(commentList[i]) free(commentList[i]);
18606                 commentList[i] = NULL;
18607         }
18608         framePtr = MAX_MOVES-1;
18609         storedGames = 0;
18610 }
18611
18612 void
18613 LoadVariation (int index, char *text)
18614 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18615         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18616         int level = 0, move;
18617
18618         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18619         // first find outermost bracketing variation
18620         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18621             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18622                 if(*p == '{') wait = '}'; else
18623                 if(*p == '[') wait = ']'; else
18624                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18625                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18626             }
18627             if(*p == wait) wait = NULLCHAR; // closing ]} found
18628             p++;
18629         }
18630         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18631         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18632         end[1] = NULLCHAR; // clip off comment beyond variation
18633         ToNrEvent(currentMove-1);
18634         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18635         // kludge: use ParsePV() to append variation to game
18636         move = currentMove;
18637         ParsePV(start, TRUE, TRUE);
18638         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18639         ClearPremoveHighlights();
18640         CommentPopDown();
18641         ToNrEvent(currentMove+1);
18642 }
18643
18644 void
18645 LoadTheme ()
18646 {
18647     char *p, *q, buf[MSG_SIZ];
18648     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18649         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18650         ParseArgsFromString(buf);
18651         ActivateTheme(TRUE); // also redo colors
18652         return;
18653     }
18654     p = nickName;
18655     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18656     {
18657         int len;
18658         q = appData.themeNames;
18659         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18660       if(appData.useBitmaps) {
18661         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18662                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18663                 appData.liteBackTextureMode,
18664                 appData.darkBackTextureMode );
18665       } else {
18666         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18667                 Col2Text(2),   // lightSquareColor
18668                 Col2Text(3) ); // darkSquareColor
18669       }
18670       if(appData.useBorder) {
18671         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18672                 appData.border);
18673       } else {
18674         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18675       }
18676       if(appData.useFont) {
18677         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18678                 appData.renderPiecesWithFont,
18679                 appData.fontToPieceTable,
18680                 Col2Text(9),    // appData.fontBackColorWhite
18681                 Col2Text(10) ); // appData.fontForeColorBlack
18682       } else {
18683         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18684                 appData.pieceDirectory);
18685         if(!appData.pieceDirectory[0])
18686           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18687                 Col2Text(0),   // whitePieceColor
18688                 Col2Text(1) ); // blackPieceColor
18689       }
18690       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18691                 Col2Text(4),   // highlightSquareColor
18692                 Col2Text(5) ); // premoveHighlightColor
18693         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18694         if(insert != q) insert[-1] = NULLCHAR;
18695         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18696         if(q)   free(q);
18697     }
18698     ActivateTheme(FALSE);
18699 }