Implement non-royal castling
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58     int flock(int f, int code);
59 #   define LOCK_EX 2
60 #   define SLASH '\\'
61
62 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299
300 /* States for ics_getting_history */
301 #define H_FALSE 0
302 #define H_REQUESTED 1
303 #define H_GOT_REQ_HEADER 2
304 #define H_GOT_UNREQ_HEADER 3
305 #define H_GETTING_MOVES 4
306 #define H_GOT_UNWANTED_HEADER 5
307
308 /* whosays values for GameEnds */
309 #define GE_ICS 0
310 #define GE_ENGINE 1
311 #define GE_PLAYER 2
312 #define GE_FILE 3
313 #define GE_XBOARD 4
314 #define GE_ENGINE1 5
315 #define GE_ENGINE2 6
316
317 /* Maximum number of games in a cmail message */
318 #define CMAIL_MAX_GAMES 20
319
320 /* Different types of move when calling RegisterMove */
321 #define CMAIL_MOVE   0
322 #define CMAIL_RESIGN 1
323 #define CMAIL_DRAW   2
324 #define CMAIL_ACCEPT 3
325
326 /* Different types of result to remember for each game */
327 #define CMAIL_NOT_RESULT 0
328 #define CMAIL_OLD_RESULT 1
329 #define CMAIL_NEW_RESULT 2
330
331 /* Telnet protocol constants */
332 #define TN_WILL 0373
333 #define TN_WONT 0374
334 #define TN_DO   0375
335 #define TN_DONT 0376
336 #define TN_IAC  0377
337 #define TN_ECHO 0001
338 #define TN_SGA  0003
339 #define TN_PORT 23
340
341 char*
342 safeStrCpy (char *dst, const char *src, size_t count)
343 { // [HGM] made safe
344   int i;
345   assert( dst != NULL );
346   assert( src != NULL );
347   assert( count > 0 );
348
349   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
350   if(  i == count && dst[count-1] != NULLCHAR)
351     {
352       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
353       if(appData.debugMode)
354         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
355     }
356
357   return dst;
358 }
359
360 /* Some compiler can't cast u64 to double
361  * This function do the job for us:
362
363  * We use the highest bit for cast, this only
364  * works if the highest bit is not
365  * in use (This should not happen)
366  *
367  * We used this for all compiler
368  */
369 double
370 u64ToDouble (u64 value)
371 {
372   double r;
373   u64 tmp = value & u64Const(0x7fffffffffffffff);
374   r = (double)(s64)tmp;
375   if (value & u64Const(0x8000000000000000))
376        r +=  9.2233720368547758080e18; /* 2^63 */
377  return r;
378 }
379
380 /* Fake up flags for now, as we aren't keeping track of castling
381    availability yet. [HGM] Change of logic: the flag now only
382    indicates the type of castlings allowed by the rule of the game.
383    The actual rights themselves are maintained in the array
384    castlingRights, as part of the game history, and are not probed
385    by this function.
386  */
387 int
388 PosFlags (index)
389 {
390   int flags = F_ALL_CASTLE_OK;
391   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
392   switch (gameInfo.variant) {
393   case VariantSuicide:
394     flags &= ~F_ALL_CASTLE_OK;
395   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
396     flags |= F_IGNORE_CHECK;
397   case VariantLosers:
398     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
399     break;
400   case VariantAtomic:
401     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
402     break;
403   case VariantKriegspiel:
404     flags |= F_KRIEGSPIEL_CAPTURE;
405     break;
406   case VariantCapaRandom:
407   case VariantFischeRandom:
408     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
409   case VariantNoCastle:
410   case VariantShatranj:
411   case VariantCourier:
412   case VariantMakruk:
413   case VariantASEAN:
414   case VariantGrand:
415     flags &= ~F_ALL_CASTLE_OK;
416     break;
417   case VariantChu:
418   case VariantChuChess:
419   case VariantLion:
420     flags |= F_NULL_MOVE;
421     break;
422   default:
423     break;
424   }
425   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
426   return flags;
427 }
428
429 FILE *gameFileFP, *debugFP, *serverFP;
430 char *currentDebugFile; // [HGM] debug split: to remember name
431
432 /*
433     [AS] Note: sometimes, the sscanf() function is used to parse the input
434     into a fixed-size buffer. Because of this, we must be prepared to
435     receive strings as long as the size of the input buffer, which is currently
436     set to 4K for Windows and 8K for the rest.
437     So, we must either allocate sufficiently large buffers here, or
438     reduce the size of the input buffer in the input reading part.
439 */
440
441 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
442 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
443 char thinkOutput1[MSG_SIZ*10];
444
445 ChessProgramState first, second, pairing;
446
447 /* premove variables */
448 int premoveToX = 0;
449 int premoveToY = 0;
450 int premoveFromX = 0;
451 int premoveFromY = 0;
452 int premovePromoChar = 0;
453 int gotPremove = 0;
454 Boolean alarmSounded;
455 /* end premove variables */
456
457 char *ics_prefix = "$";
458 enum ICS_TYPE ics_type = ICS_GENERIC;
459
460 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
461 int pauseExamForwardMostMove = 0;
462 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
463 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
464 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
465 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
466 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
467 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
468 int whiteFlag = FALSE, blackFlag = FALSE;
469 int userOfferedDraw = FALSE;
470 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
471 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
472 int cmailMoveType[CMAIL_MAX_GAMES];
473 long ics_clock_paused = 0;
474 ProcRef icsPR = NoProc, cmailPR = NoProc;
475 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
476 GameMode gameMode = BeginningOfGame;
477 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
478 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
479 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
480 int hiddenThinkOutputState = 0; /* [AS] */
481 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
482 int adjudicateLossPlies = 6;
483 char white_holding[64], black_holding[64];
484 TimeMark lastNodeCountTime;
485 long lastNodeCount=0;
486 int shiftKey, controlKey; // [HGM] set by mouse handler
487
488 int have_sent_ICS_logon = 0;
489 int movesPerSession;
490 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
491 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
492 Boolean adjustedClock;
493 long timeControl_2; /* [AS] Allow separate time controls */
494 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
495 long timeRemaining[2][MAX_MOVES];
496 int matchGame = 0, nextGame = 0, roundNr = 0;
497 Boolean waitingForGame = FALSE, startingEngine = FALSE;
498 TimeMark programStartTime, pauseStart;
499 char ics_handle[MSG_SIZ];
500 int have_set_title = 0;
501
502 /* animateTraining preserves the state of appData.animate
503  * when Training mode is activated. This allows the
504  * response to be animated when appData.animate == TRUE and
505  * appData.animateDragging == TRUE.
506  */
507 Boolean animateTraining;
508
509 GameInfo gameInfo;
510
511 AppData appData;
512
513 Board boards[MAX_MOVES];
514 /* [HGM] Following 7 needed for accurate legality tests: */
515 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
516 signed char  initialRights[BOARD_FILES];
517 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
518 int   initialRulePlies, FENrulePlies;
519 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
520 int loadFlag = 0;
521 Boolean shuffleOpenings;
522 int mute; // mute all sounds
523
524 // [HGM] vari: next 12 to save and restore variations
525 #define MAX_VARIATIONS 10
526 int framePtr = MAX_MOVES-1; // points to free stack entry
527 int storedGames = 0;
528 int savedFirst[MAX_VARIATIONS];
529 int savedLast[MAX_VARIATIONS];
530 int savedFramePtr[MAX_VARIATIONS];
531 char *savedDetails[MAX_VARIATIONS];
532 ChessMove savedResult[MAX_VARIATIONS];
533
534 void PushTail P((int firstMove, int lastMove));
535 Boolean PopTail P((Boolean annotate));
536 void PushInner P((int firstMove, int lastMove));
537 void PopInner P((Boolean annotate));
538 void CleanupTail P((void));
539
540 ChessSquare  FIDEArray[2][BOARD_FILES] = {
541     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
542         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
544         BlackKing, BlackBishop, BlackKnight, BlackRook }
545 };
546
547 ChessSquare twoKingsArray[2][BOARD_FILES] = {
548     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
549         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
551         BlackKing, BlackKing, BlackKnight, BlackRook }
552 };
553
554 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
555     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
556         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
557     { BlackRook, BlackMan, BlackBishop, BlackQueen,
558         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
559 };
560
561 ChessSquare SpartanArray[2][BOARD_FILES] = {
562     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
563         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
564     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
565         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
566 };
567
568 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
569     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
570         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
571     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
572         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
573 };
574
575 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
576     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
577         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
579         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 };
581
582 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
583     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
584         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
585     { BlackRook, BlackKnight, BlackMan, BlackFerz,
586         BlackKing, BlackMan, BlackKnight, BlackRook }
587 };
588
589 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
590     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
591         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
592     { BlackRook, BlackKnight, BlackMan, BlackFerz,
593         BlackKing, BlackMan, BlackKnight, BlackRook }
594 };
595
596 ChessSquare  lionArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
598         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackLion, BlackBishop, BlackQueen,
600         BlackKing, BlackBishop, BlackKnight, BlackRook }
601 };
602
603
604 #if (BOARD_FILES>=10)
605 ChessSquare ShogiArray[2][BOARD_FILES] = {
606     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
607         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
608     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
609         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
610 };
611
612 ChessSquare XiangqiArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
614         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
616         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
617 };
618
619 ChessSquare CapablancaArray[2][BOARD_FILES] = {
620     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
621         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
622     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
623         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
624 };
625
626 ChessSquare GreatArray[2][BOARD_FILES] = {
627     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
628         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
629     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
630         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
631 };
632
633 ChessSquare JanusArray[2][BOARD_FILES] = {
634     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
635         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
636     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
637         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
638 };
639
640 ChessSquare GrandArray[2][BOARD_FILES] = {
641     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
642         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
643     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
644         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
645 };
646
647 ChessSquare ChuChessArray[2][BOARD_FILES] = {
648     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
649         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
650     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
651         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
652 };
653
654 #ifdef GOTHIC
655 ChessSquare GothicArray[2][BOARD_FILES] = {
656     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
657         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
658     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
659         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
660 };
661 #else // !GOTHIC
662 #define GothicArray CapablancaArray
663 #endif // !GOTHIC
664
665 #ifdef FALCON
666 ChessSquare FalconArray[2][BOARD_FILES] = {
667     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
668         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
669     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
670         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
671 };
672 #else // !FALCON
673 #define FalconArray CapablancaArray
674 #endif // !FALCON
675
676 #else // !(BOARD_FILES>=10)
677 #define XiangqiPosition FIDEArray
678 #define CapablancaArray FIDEArray
679 #define GothicArray FIDEArray
680 #define GreatArray FIDEArray
681 #endif // !(BOARD_FILES>=10)
682
683 #if (BOARD_FILES>=12)
684 ChessSquare CourierArray[2][BOARD_FILES] = {
685     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
686         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
687     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
688         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
689 };
690 ChessSquare ChuArray[6][BOARD_FILES] = {
691     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
692       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
693     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
694       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
695     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
696       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
697     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
698       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
699     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
700       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
701     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
702       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
703 };
704 #else // !(BOARD_FILES>=12)
705 #define CourierArray CapablancaArray
706 #define ChuArray CapablancaArray
707 #endif // !(BOARD_FILES>=12)
708
709
710 Board initialPosition;
711
712
713 /* Convert str to a rating. Checks for special cases of "----",
714
715    "++++", etc. Also strips ()'s */
716 int
717 string_to_rating (char *str)
718 {
719   while(*str && !isdigit(*str)) ++str;
720   if (!*str)
721     return 0;   /* One of the special "no rating" cases */
722   else
723     return atoi(str);
724 }
725
726 void
727 ClearProgramStats ()
728 {
729     /* Init programStats */
730     programStats.movelist[0] = 0;
731     programStats.depth = 0;
732     programStats.nr_moves = 0;
733     programStats.moves_left = 0;
734     programStats.nodes = 0;
735     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
736     programStats.score = 0;
737     programStats.got_only_move = 0;
738     programStats.got_fail = 0;
739     programStats.line_is_book = 0;
740 }
741
742 void
743 CommonEngineInit ()
744 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
745     if (appData.firstPlaysBlack) {
746         first.twoMachinesColor = "black\n";
747         second.twoMachinesColor = "white\n";
748     } else {
749         first.twoMachinesColor = "white\n";
750         second.twoMachinesColor = "black\n";
751     }
752
753     first.other = &second;
754     second.other = &first;
755
756     { float norm = 1;
757         if(appData.timeOddsMode) {
758             norm = appData.timeOdds[0];
759             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
760         }
761         first.timeOdds  = appData.timeOdds[0]/norm;
762         second.timeOdds = appData.timeOdds[1]/norm;
763     }
764
765     if(programVersion) free(programVersion);
766     if (appData.noChessProgram) {
767         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
768         sprintf(programVersion, "%s", PACKAGE_STRING);
769     } else {
770       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
771       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
772       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
773     }
774 }
775
776 void
777 UnloadEngine (ChessProgramState *cps)
778 {
779         /* Kill off first chess program */
780         if (cps->isr != NULL)
781           RemoveInputSource(cps->isr);
782         cps->isr = NULL;
783
784         if (cps->pr != NoProc) {
785             ExitAnalyzeMode();
786             DoSleep( appData.delayBeforeQuit );
787             SendToProgram("quit\n", cps);
788             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
789         }
790         cps->pr = NoProc;
791         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
792 }
793
794 void
795 ClearOptions (ChessProgramState *cps)
796 {
797     int i;
798     cps->nrOptions = cps->comboCnt = 0;
799     for(i=0; i<MAX_OPTIONS; i++) {
800         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
801         cps->option[i].textValue = 0;
802     }
803 }
804
805 char *engineNames[] = {
806   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
807      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
808 N_("first"),
809   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("second")
812 };
813
814 void
815 InitEngine (ChessProgramState *cps, int n)
816 {   // [HGM] all engine initialiation put in a function that does one engine
817
818     ClearOptions(cps);
819
820     cps->which = engineNames[n];
821     cps->maybeThinking = FALSE;
822     cps->pr = NoProc;
823     cps->isr = NULL;
824     cps->sendTime = 2;
825     cps->sendDrawOffers = 1;
826
827     cps->program = appData.chessProgram[n];
828     cps->host = appData.host[n];
829     cps->dir = appData.directory[n];
830     cps->initString = appData.engInitString[n];
831     cps->computerString = appData.computerString[n];
832     cps->useSigint  = TRUE;
833     cps->useSigterm = TRUE;
834     cps->reuse = appData.reuse[n];
835     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
836     cps->useSetboard = FALSE;
837     cps->useSAN = FALSE;
838     cps->usePing = FALSE;
839     cps->lastPing = 0;
840     cps->lastPong = 0;
841     cps->usePlayother = FALSE;
842     cps->useColors = TRUE;
843     cps->useUsermove = FALSE;
844     cps->sendICS = FALSE;
845     cps->sendName = appData.icsActive;
846     cps->sdKludge = FALSE;
847     cps->stKludge = FALSE;
848     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
849     TidyProgramName(cps->program, cps->host, cps->tidy);
850     cps->matchWins = 0;
851     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
852     cps->analysisSupport = 2; /* detect */
853     cps->analyzing = FALSE;
854     cps->initDone = FALSE;
855     cps->reload = FALSE;
856     cps->pseudo = appData.pseudo[n];
857
858     /* New features added by Tord: */
859     cps->useFEN960 = FALSE;
860     cps->useOOCastle = TRUE;
861     /* End of new features added by Tord. */
862     cps->fenOverride  = appData.fenOverride[n];
863
864     /* [HGM] time odds: set factor for each machine */
865     cps->timeOdds  = appData.timeOdds[n];
866
867     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
868     cps->accumulateTC = appData.accumulateTC[n];
869     cps->maxNrOfSessions = 1;
870
871     /* [HGM] debug */
872     cps->debug = FALSE;
873
874     cps->drawDepth = appData.drawDepth[n];
875     cps->supportsNPS = UNKNOWN;
876     cps->memSize = FALSE;
877     cps->maxCores = FALSE;
878     ASSIGN(cps->egtFormats, "");
879
880     /* [HGM] options */
881     cps->optionSettings  = appData.engOptions[n];
882
883     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
884     cps->isUCI = appData.isUCI[n]; /* [AS] */
885     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
886     cps->highlight = 0;
887
888     if (appData.protocolVersion[n] > PROTOVER
889         || appData.protocolVersion[n] < 1)
890       {
891         char buf[MSG_SIZ];
892         int len;
893
894         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
895                        appData.protocolVersion[n]);
896         if( (len >= MSG_SIZ) && appData.debugMode )
897           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
898
899         DisplayFatalError(buf, 0, 2);
900       }
901     else
902       {
903         cps->protocolVersion = appData.protocolVersion[n];
904       }
905
906     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
907     ParseFeatures(appData.featureDefaults, cps);
908 }
909
910 ChessProgramState *savCps;
911
912 GameMode oldMode;
913
914 void
915 LoadEngine ()
916 {
917     int i;
918     if(WaitForEngine(savCps, LoadEngine)) return;
919     CommonEngineInit(); // recalculate time odds
920     if(gameInfo.variant != StringToVariant(appData.variant)) {
921         // we changed variant when loading the engine; this forces us to reset
922         Reset(TRUE, savCps != &first);
923         oldMode = BeginningOfGame; // to prevent restoring old mode
924     }
925     InitChessProgram(savCps, FALSE);
926     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
927     DisplayMessage("", "");
928     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
929     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
930     ThawUI();
931     SetGNUMode();
932     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
933 }
934
935 void
936 ReplaceEngine (ChessProgramState *cps, int n)
937 {
938     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
939     keepInfo = 1;
940     if(oldMode != BeginningOfGame) EditGameEvent();
941     keepInfo = 0;
942     UnloadEngine(cps);
943     appData.noChessProgram = FALSE;
944     appData.clockMode = TRUE;
945     InitEngine(cps, n);
946     UpdateLogos(TRUE);
947     if(n) return; // only startup first engine immediately; second can wait
948     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
949     LoadEngine();
950 }
951
952 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
953 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
954
955 static char resetOptions[] =
956         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
957         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
958         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
959         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
960
961 void
962 FloatToFront(char **list, char *engineLine)
963 {
964     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
965     int i=0;
966     if(appData.recentEngines <= 0) return;
967     TidyProgramName(engineLine, "localhost", tidy+1);
968     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
969     strncpy(buf+1, *list, MSG_SIZ-50);
970     if(p = strstr(buf, tidy)) { // tidy name appears in list
971         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
972         while(*p++ = *++q); // squeeze out
973     }
974     strcat(tidy, buf+1); // put list behind tidy name
975     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
976     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
977     ASSIGN(*list, tidy+1);
978 }
979
980 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
981
982 void
983 Load (ChessProgramState *cps, int i)
984 {
985     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
986     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
987         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
988         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
989         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
990         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
991         appData.firstProtocolVersion = PROTOVER;
992         ParseArgsFromString(buf);
993         SwapEngines(i);
994         ReplaceEngine(cps, i);
995         FloatToFront(&appData.recentEngineList, engineLine);
996         return;
997     }
998     p = engineName;
999     while(q = strchr(p, SLASH)) p = q+1;
1000     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1001     if(engineDir[0] != NULLCHAR) {
1002         ASSIGN(appData.directory[i], engineDir); p = engineName;
1003     } else if(p != engineName) { // derive directory from engine path, when not given
1004         p[-1] = 0;
1005         ASSIGN(appData.directory[i], engineName);
1006         p[-1] = SLASH;
1007         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1008     } else { ASSIGN(appData.directory[i], "."); }
1009     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1010     if(params[0]) {
1011         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1012         snprintf(command, MSG_SIZ, "%s %s", p, params);
1013         p = command;
1014     }
1015     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1016     ASSIGN(appData.chessProgram[i], p);
1017     appData.isUCI[i] = isUCI;
1018     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1019     appData.hasOwnBookUCI[i] = hasBook;
1020     if(!nickName[0]) useNick = FALSE;
1021     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1022     if(addToList) {
1023         int len;
1024         char quote;
1025         q = firstChessProgramNames;
1026         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1027         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1028         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1029                         quote, p, quote, appData.directory[i],
1030                         useNick ? " -fn \"" : "",
1031                         useNick ? nickName : "",
1032                         useNick ? "\"" : "",
1033                         v1 ? " -firstProtocolVersion 1" : "",
1034                         hasBook ? "" : " -fNoOwnBookUCI",
1035                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1036                         storeVariant ? " -variant " : "",
1037                         storeVariant ? VariantName(gameInfo.variant) : "");
1038         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1039         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1040         if(insert != q) insert[-1] = NULLCHAR;
1041         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1042         if(q)   free(q);
1043         FloatToFront(&appData.recentEngineList, buf);
1044     }
1045     ReplaceEngine(cps, i);
1046 }
1047
1048 void
1049 InitTimeControls ()
1050 {
1051     int matched, min, sec;
1052     /*
1053      * Parse timeControl resource
1054      */
1055     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1056                           appData.movesPerSession)) {
1057         char buf[MSG_SIZ];
1058         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1059         DisplayFatalError(buf, 0, 2);
1060     }
1061
1062     /*
1063      * Parse searchTime resource
1064      */
1065     if (*appData.searchTime != NULLCHAR) {
1066         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1067         if (matched == 1) {
1068             searchTime = min * 60;
1069         } else if (matched == 2) {
1070             searchTime = min * 60 + sec;
1071         } else {
1072             char buf[MSG_SIZ];
1073             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1074             DisplayFatalError(buf, 0, 2);
1075         }
1076     }
1077 }
1078
1079 void
1080 InitBackEnd1 ()
1081 {
1082
1083     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1084     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1085
1086     GetTimeMark(&programStartTime);
1087     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1088     appData.seedBase = random() + (random()<<15);
1089     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1090
1091     ClearProgramStats();
1092     programStats.ok_to_send = 1;
1093     programStats.seen_stat = 0;
1094
1095     /*
1096      * Initialize game list
1097      */
1098     ListNew(&gameList);
1099
1100
1101     /*
1102      * Internet chess server status
1103      */
1104     if (appData.icsActive) {
1105         appData.matchMode = FALSE;
1106         appData.matchGames = 0;
1107 #if ZIPPY
1108         appData.noChessProgram = !appData.zippyPlay;
1109 #else
1110         appData.zippyPlay = FALSE;
1111         appData.zippyTalk = FALSE;
1112         appData.noChessProgram = TRUE;
1113 #endif
1114         if (*appData.icsHelper != NULLCHAR) {
1115             appData.useTelnet = TRUE;
1116             appData.telnetProgram = appData.icsHelper;
1117         }
1118     } else {
1119         appData.zippyTalk = appData.zippyPlay = FALSE;
1120     }
1121
1122     /* [AS] Initialize pv info list [HGM] and game state */
1123     {
1124         int i, j;
1125
1126         for( i=0; i<=framePtr; i++ ) {
1127             pvInfoList[i].depth = -1;
1128             boards[i][EP_STATUS] = EP_NONE;
1129             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1130         }
1131     }
1132
1133     InitTimeControls();
1134
1135     /* [AS] Adjudication threshold */
1136     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1137
1138     InitEngine(&first, 0);
1139     InitEngine(&second, 1);
1140     CommonEngineInit();
1141
1142     pairing.which = "pairing"; // pairing engine
1143     pairing.pr = NoProc;
1144     pairing.isr = NULL;
1145     pairing.program = appData.pairingEngine;
1146     pairing.host = "localhost";
1147     pairing.dir = ".";
1148
1149     if (appData.icsActive) {
1150         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1151     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1152         appData.clockMode = FALSE;
1153         first.sendTime = second.sendTime = 0;
1154     }
1155
1156 #if ZIPPY
1157     /* Override some settings from environment variables, for backward
1158        compatibility.  Unfortunately it's not feasible to have the env
1159        vars just set defaults, at least in xboard.  Ugh.
1160     */
1161     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1162       ZippyInit();
1163     }
1164 #endif
1165
1166     if (!appData.icsActive) {
1167       char buf[MSG_SIZ];
1168       int len;
1169
1170       /* Check for variants that are supported only in ICS mode,
1171          or not at all.  Some that are accepted here nevertheless
1172          have bugs; see comments below.
1173       */
1174       VariantClass variant = StringToVariant(appData.variant);
1175       switch (variant) {
1176       case VariantBughouse:     /* need four players and two boards */
1177       case VariantKriegspiel:   /* need to hide pieces and move details */
1178         /* case VariantFischeRandom: (Fabien: moved below) */
1179         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1180         if( (len >= MSG_SIZ) && appData.debugMode )
1181           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1182
1183         DisplayFatalError(buf, 0, 2);
1184         return;
1185
1186       case VariantUnknown:
1187       case VariantLoadable:
1188       case Variant29:
1189       case Variant30:
1190       case Variant31:
1191       case Variant32:
1192       case Variant33:
1193       case Variant34:
1194       case Variant35:
1195       case Variant36:
1196       default:
1197         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1198         if( (len >= MSG_SIZ) && appData.debugMode )
1199           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1200
1201         DisplayFatalError(buf, 0, 2);
1202         return;
1203
1204       case VariantNormal:     /* definitely works! */
1205         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1206           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1207           return;
1208         }
1209       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1210       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1211       case VariantGothic:     /* [HGM] should work */
1212       case VariantCapablanca: /* [HGM] should work */
1213       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1214       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1215       case VariantChu:        /* [HGM] experimental */
1216       case VariantKnightmate: /* [HGM] should work */
1217       case VariantCylinder:   /* [HGM] untested */
1218       case VariantFalcon:     /* [HGM] untested */
1219       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1220                                  offboard interposition not understood */
1221       case VariantWildCastle: /* pieces not automatically shuffled */
1222       case VariantNoCastle:   /* pieces not automatically shuffled */
1223       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1224       case VariantLosers:     /* should work except for win condition,
1225                                  and doesn't know captures are mandatory */
1226       case VariantSuicide:    /* should work except for win condition,
1227                                  and doesn't know captures are mandatory */
1228       case VariantGiveaway:   /* should work except for win condition,
1229                                  and doesn't know captures are mandatory */
1230       case VariantTwoKings:   /* should work */
1231       case VariantAtomic:     /* should work except for win condition */
1232       case Variant3Check:     /* should work except for win condition */
1233       case VariantShatranj:   /* should work except for all win conditions */
1234       case VariantMakruk:     /* should work except for draw countdown */
1235       case VariantASEAN :     /* should work except for draw countdown */
1236       case VariantBerolina:   /* might work if TestLegality is off */
1237       case VariantCapaRandom: /* should work */
1238       case VariantJanus:      /* should work */
1239       case VariantSuper:      /* experimental */
1240       case VariantGreat:      /* experimental, requires legality testing to be off */
1241       case VariantSChess:     /* S-Chess, should work */
1242       case VariantGrand:      /* should work */
1243       case VariantSpartan:    /* should work */
1244       case VariantLion:       /* should work */
1245       case VariantChuChess:   /* should work */
1246         break;
1247       }
1248     }
1249
1250 }
1251
1252 int
1253 NextIntegerFromString (char ** str, long * value)
1254 {
1255     int result = -1;
1256     char * s = *str;
1257
1258     while( *s == ' ' || *s == '\t' ) {
1259         s++;
1260     }
1261
1262     *value = 0;
1263
1264     if( *s >= '0' && *s <= '9' ) {
1265         while( *s >= '0' && *s <= '9' ) {
1266             *value = *value * 10 + (*s - '0');
1267             s++;
1268         }
1269
1270         result = 0;
1271     }
1272
1273     *str = s;
1274
1275     return result;
1276 }
1277
1278 int
1279 NextTimeControlFromString (char ** str, long * value)
1280 {
1281     long temp;
1282     int result = NextIntegerFromString( str, &temp );
1283
1284     if( result == 0 ) {
1285         *value = temp * 60; /* Minutes */
1286         if( **str == ':' ) {
1287             (*str)++;
1288             result = NextIntegerFromString( str, &temp );
1289             *value += temp; /* Seconds */
1290         }
1291     }
1292
1293     return result;
1294 }
1295
1296 int
1297 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1298 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1299     int result = -1, type = 0; long temp, temp2;
1300
1301     if(**str != ':') return -1; // old params remain in force!
1302     (*str)++;
1303     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1304     if( NextIntegerFromString( str, &temp ) ) return -1;
1305     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1306
1307     if(**str != '/') {
1308         /* time only: incremental or sudden-death time control */
1309         if(**str == '+') { /* increment follows; read it */
1310             (*str)++;
1311             if(**str == '!') type = *(*str)++; // Bronstein TC
1312             if(result = NextIntegerFromString( str, &temp2)) return -1;
1313             *inc = temp2 * 1000;
1314             if(**str == '.') { // read fraction of increment
1315                 char *start = ++(*str);
1316                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1317                 temp2 *= 1000;
1318                 while(start++ < *str) temp2 /= 10;
1319                 *inc += temp2;
1320             }
1321         } else *inc = 0;
1322         *moves = 0; *tc = temp * 1000; *incType = type;
1323         return 0;
1324     }
1325
1326     (*str)++; /* classical time control */
1327     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1328
1329     if(result == 0) {
1330         *moves = temp;
1331         *tc    = temp2 * 1000;
1332         *inc   = 0;
1333         *incType = type;
1334     }
1335     return result;
1336 }
1337
1338 int
1339 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1340 {   /* [HGM] get time to add from the multi-session time-control string */
1341     int incType, moves=1; /* kludge to force reading of first session */
1342     long time, increment;
1343     char *s = tcString;
1344
1345     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1346     do {
1347         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1348         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1349         if(movenr == -1) return time;    /* last move before new session     */
1350         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1351         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1352         if(!moves) return increment;     /* current session is incremental   */
1353         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1354     } while(movenr >= -1);               /* try again for next session       */
1355
1356     return 0; // no new time quota on this move
1357 }
1358
1359 int
1360 ParseTimeControl (char *tc, float ti, int mps)
1361 {
1362   long tc1;
1363   long tc2;
1364   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1365   int min, sec=0;
1366
1367   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1368   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1369       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1370   if(ti > 0) {
1371
1372     if(mps)
1373       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1374     else
1375       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1376   } else {
1377     if(mps)
1378       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1379     else
1380       snprintf(buf, MSG_SIZ, ":%s", mytc);
1381   }
1382   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1383
1384   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1385     return FALSE;
1386   }
1387
1388   if( *tc == '/' ) {
1389     /* Parse second time control */
1390     tc++;
1391
1392     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1393       return FALSE;
1394     }
1395
1396     if( tc2 == 0 ) {
1397       return FALSE;
1398     }
1399
1400     timeControl_2 = tc2 * 1000;
1401   }
1402   else {
1403     timeControl_2 = 0;
1404   }
1405
1406   if( tc1 == 0 ) {
1407     return FALSE;
1408   }
1409
1410   timeControl = tc1 * 1000;
1411
1412   if (ti >= 0) {
1413     timeIncrement = ti * 1000;  /* convert to ms */
1414     movesPerSession = 0;
1415   } else {
1416     timeIncrement = 0;
1417     movesPerSession = mps;
1418   }
1419   return TRUE;
1420 }
1421
1422 void
1423 InitBackEnd2 ()
1424 {
1425     if (appData.debugMode) {
1426 #    ifdef __GIT_VERSION
1427       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1428 #    else
1429       fprintf(debugFP, "Version: %s\n", programVersion);
1430 #    endif
1431     }
1432     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1433
1434     set_cont_sequence(appData.wrapContSeq);
1435     if (appData.matchGames > 0) {
1436         appData.matchMode = TRUE;
1437     } else if (appData.matchMode) {
1438         appData.matchGames = 1;
1439     }
1440     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1441         appData.matchGames = appData.sameColorGames;
1442     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1443         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1444         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1445     }
1446     Reset(TRUE, FALSE);
1447     if (appData.noChessProgram || first.protocolVersion == 1) {
1448       InitBackEnd3();
1449     } else {
1450       /* kludge: allow timeout for initial "feature" commands */
1451       FreezeUI();
1452       DisplayMessage("", _("Starting chess program"));
1453       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1454     }
1455 }
1456
1457 int
1458 CalculateIndex (int index, int gameNr)
1459 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1460     int res;
1461     if(index > 0) return index; // fixed nmber
1462     if(index == 0) return 1;
1463     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1464     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1465     return res;
1466 }
1467
1468 int
1469 LoadGameOrPosition (int gameNr)
1470 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1471     if (*appData.loadGameFile != NULLCHAR) {
1472         if (!LoadGameFromFile(appData.loadGameFile,
1473                 CalculateIndex(appData.loadGameIndex, gameNr),
1474                               appData.loadGameFile, FALSE)) {
1475             DisplayFatalError(_("Bad game file"), 0, 1);
1476             return 0;
1477         }
1478     } else if (*appData.loadPositionFile != NULLCHAR) {
1479         if (!LoadPositionFromFile(appData.loadPositionFile,
1480                 CalculateIndex(appData.loadPositionIndex, gameNr),
1481                                   appData.loadPositionFile)) {
1482             DisplayFatalError(_("Bad position file"), 0, 1);
1483             return 0;
1484         }
1485     }
1486     return 1;
1487 }
1488
1489 void
1490 ReserveGame (int gameNr, char resChar)
1491 {
1492     FILE *tf = fopen(appData.tourneyFile, "r+");
1493     char *p, *q, c, buf[MSG_SIZ];
1494     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1495     safeStrCpy(buf, lastMsg, MSG_SIZ);
1496     DisplayMessage(_("Pick new game"), "");
1497     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1498     ParseArgsFromFile(tf);
1499     p = q = appData.results;
1500     if(appData.debugMode) {
1501       char *r = appData.participants;
1502       fprintf(debugFP, "results = '%s'\n", p);
1503       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1504       fprintf(debugFP, "\n");
1505     }
1506     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1507     nextGame = q - p;
1508     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1509     safeStrCpy(q, p, strlen(p) + 2);
1510     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1511     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1512     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1513         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1514         q[nextGame] = '*';
1515     }
1516     fseek(tf, -(strlen(p)+4), SEEK_END);
1517     c = fgetc(tf);
1518     if(c != '"') // depending on DOS or Unix line endings we can be one off
1519          fseek(tf, -(strlen(p)+2), SEEK_END);
1520     else fseek(tf, -(strlen(p)+3), SEEK_END);
1521     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1522     DisplayMessage(buf, "");
1523     free(p); appData.results = q;
1524     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1525        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1526       int round = appData.defaultMatchGames * appData.tourneyType;
1527       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1528          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1529         UnloadEngine(&first);  // next game belongs to other pairing;
1530         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1531     }
1532     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1533 }
1534
1535 void
1536 MatchEvent (int mode)
1537 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1538         int dummy;
1539         if(matchMode) { // already in match mode: switch it off
1540             abortMatch = TRUE;
1541             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1542             return;
1543         }
1544 //      if(gameMode != BeginningOfGame) {
1545 //          DisplayError(_("You can only start a match from the initial position."), 0);
1546 //          return;
1547 //      }
1548         abortMatch = FALSE;
1549         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1550         /* Set up machine vs. machine match */
1551         nextGame = 0;
1552         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1553         if(appData.tourneyFile[0]) {
1554             ReserveGame(-1, 0);
1555             if(nextGame > appData.matchGames) {
1556                 char buf[MSG_SIZ];
1557                 if(strchr(appData.results, '*') == NULL) {
1558                     FILE *f;
1559                     appData.tourneyCycles++;
1560                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1561                         fclose(f);
1562                         NextTourneyGame(-1, &dummy);
1563                         ReserveGame(-1, 0);
1564                         if(nextGame <= appData.matchGames) {
1565                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1566                             matchMode = mode;
1567                             ScheduleDelayedEvent(NextMatchGame, 10000);
1568                             return;
1569                         }
1570                     }
1571                 }
1572                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1573                 DisplayError(buf, 0);
1574                 appData.tourneyFile[0] = 0;
1575                 return;
1576             }
1577         } else
1578         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1579             DisplayFatalError(_("Can't have a match with no chess programs"),
1580                               0, 2);
1581             return;
1582         }
1583         matchMode = mode;
1584         matchGame = roundNr = 1;
1585         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1586         NextMatchGame();
1587 }
1588
1589 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1590
1591 void
1592 InitBackEnd3 P((void))
1593 {
1594     GameMode initialMode;
1595     char buf[MSG_SIZ];
1596     int err, len;
1597
1598     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1599        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1600         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1601        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1602        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1603         char c, *q = first.variants, *p = strchr(q, ',');
1604         if(p) *p = NULLCHAR;
1605         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1606             int w, h, s;
1607             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1608                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1609             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1610             Reset(TRUE, FALSE);         // and re-initialize
1611         }
1612         if(p) *p = ',';
1613     }
1614
1615     InitChessProgram(&first, startedFromSetupPosition);
1616
1617     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1618         free(programVersion);
1619         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1620         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1621         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1622     }
1623
1624     if (appData.icsActive) {
1625 #ifdef WIN32
1626         /* [DM] Make a console window if needed [HGM] merged ifs */
1627         ConsoleCreate();
1628 #endif
1629         err = establish();
1630         if (err != 0)
1631           {
1632             if (*appData.icsCommPort != NULLCHAR)
1633               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1634                              appData.icsCommPort);
1635             else
1636               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1637                         appData.icsHost, appData.icsPort);
1638
1639             if( (len >= MSG_SIZ) && appData.debugMode )
1640               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1641
1642             DisplayFatalError(buf, err, 1);
1643             return;
1644         }
1645         SetICSMode();
1646         telnetISR =
1647           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1648         fromUserISR =
1649           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1650         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1651             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1652     } else if (appData.noChessProgram) {
1653         SetNCPMode();
1654     } else {
1655         SetGNUMode();
1656     }
1657
1658     if (*appData.cmailGameName != NULLCHAR) {
1659         SetCmailMode();
1660         OpenLoopback(&cmailPR);
1661         cmailISR =
1662           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1663     }
1664
1665     ThawUI();
1666     DisplayMessage("", "");
1667     if (StrCaseCmp(appData.initialMode, "") == 0) {
1668       initialMode = BeginningOfGame;
1669       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1670         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1671         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1672         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1673         ModeHighlight();
1674       }
1675     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1676       initialMode = TwoMachinesPlay;
1677     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1678       initialMode = AnalyzeFile;
1679     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1680       initialMode = AnalyzeMode;
1681     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1682       initialMode = MachinePlaysWhite;
1683     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1684       initialMode = MachinePlaysBlack;
1685     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1686       initialMode = EditGame;
1687     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1688       initialMode = EditPosition;
1689     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1690       initialMode = Training;
1691     } else {
1692       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1693       if( (len >= MSG_SIZ) && appData.debugMode )
1694         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1695
1696       DisplayFatalError(buf, 0, 2);
1697       return;
1698     }
1699
1700     if (appData.matchMode) {
1701         if(appData.tourneyFile[0]) { // start tourney from command line
1702             FILE *f;
1703             if(f = fopen(appData.tourneyFile, "r")) {
1704                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1705                 fclose(f);
1706                 appData.clockMode = TRUE;
1707                 SetGNUMode();
1708             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1709         }
1710         MatchEvent(TRUE);
1711     } else if (*appData.cmailGameName != NULLCHAR) {
1712         /* Set up cmail mode */
1713         ReloadCmailMsgEvent(TRUE);
1714     } else {
1715         /* Set up other modes */
1716         if (initialMode == AnalyzeFile) {
1717           if (*appData.loadGameFile == NULLCHAR) {
1718             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1719             return;
1720           }
1721         }
1722         if (*appData.loadGameFile != NULLCHAR) {
1723             (void) LoadGameFromFile(appData.loadGameFile,
1724                                     appData.loadGameIndex,
1725                                     appData.loadGameFile, TRUE);
1726         } else if (*appData.loadPositionFile != NULLCHAR) {
1727             (void) LoadPositionFromFile(appData.loadPositionFile,
1728                                         appData.loadPositionIndex,
1729                                         appData.loadPositionFile);
1730             /* [HGM] try to make self-starting even after FEN load */
1731             /* to allow automatic setup of fairy variants with wtm */
1732             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1733                 gameMode = BeginningOfGame;
1734                 setboardSpoiledMachineBlack = 1;
1735             }
1736             /* [HGM] loadPos: make that every new game uses the setup */
1737             /* from file as long as we do not switch variant          */
1738             if(!blackPlaysFirst) {
1739                 startedFromPositionFile = TRUE;
1740                 CopyBoard(filePosition, boards[0]);
1741             }
1742         }
1743         if (initialMode == AnalyzeMode) {
1744           if (appData.noChessProgram) {
1745             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1746             return;
1747           }
1748           if (appData.icsActive) {
1749             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1750             return;
1751           }
1752           AnalyzeModeEvent();
1753         } else if (initialMode == AnalyzeFile) {
1754           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1755           ShowThinkingEvent();
1756           AnalyzeFileEvent();
1757           AnalysisPeriodicEvent(1);
1758         } else if (initialMode == MachinePlaysWhite) {
1759           if (appData.noChessProgram) {
1760             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1761                               0, 2);
1762             return;
1763           }
1764           if (appData.icsActive) {
1765             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1766                               0, 2);
1767             return;
1768           }
1769           MachineWhiteEvent();
1770         } else if (initialMode == MachinePlaysBlack) {
1771           if (appData.noChessProgram) {
1772             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1773                               0, 2);
1774             return;
1775           }
1776           if (appData.icsActive) {
1777             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1778                               0, 2);
1779             return;
1780           }
1781           MachineBlackEvent();
1782         } else if (initialMode == TwoMachinesPlay) {
1783           if (appData.noChessProgram) {
1784             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1785                               0, 2);
1786             return;
1787           }
1788           if (appData.icsActive) {
1789             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1790                               0, 2);
1791             return;
1792           }
1793           TwoMachinesEvent();
1794         } else if (initialMode == EditGame) {
1795           EditGameEvent();
1796         } else if (initialMode == EditPosition) {
1797           EditPositionEvent();
1798         } else if (initialMode == Training) {
1799           if (*appData.loadGameFile == NULLCHAR) {
1800             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1801             return;
1802           }
1803           TrainingEvent();
1804         }
1805     }
1806 }
1807
1808 void
1809 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1810 {
1811     DisplayBook(current+1);
1812
1813     MoveHistorySet( movelist, first, last, current, pvInfoList );
1814
1815     EvalGraphSet( first, last, current, pvInfoList );
1816
1817     MakeEngineOutputTitle();
1818 }
1819
1820 /*
1821  * Establish will establish a contact to a remote host.port.
1822  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1823  *  used to talk to the host.
1824  * Returns 0 if okay, error code if not.
1825  */
1826 int
1827 establish ()
1828 {
1829     char buf[MSG_SIZ];
1830
1831     if (*appData.icsCommPort != NULLCHAR) {
1832         /* Talk to the host through a serial comm port */
1833         return OpenCommPort(appData.icsCommPort, &icsPR);
1834
1835     } else if (*appData.gateway != NULLCHAR) {
1836         if (*appData.remoteShell == NULLCHAR) {
1837             /* Use the rcmd protocol to run telnet program on a gateway host */
1838             snprintf(buf, sizeof(buf), "%s %s %s",
1839                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1840             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1841
1842         } else {
1843             /* Use the rsh program to run telnet program on a gateway host */
1844             if (*appData.remoteUser == NULLCHAR) {
1845                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1846                         appData.gateway, appData.telnetProgram,
1847                         appData.icsHost, appData.icsPort);
1848             } else {
1849                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1850                         appData.remoteShell, appData.gateway,
1851                         appData.remoteUser, appData.telnetProgram,
1852                         appData.icsHost, appData.icsPort);
1853             }
1854             return StartChildProcess(buf, "", &icsPR);
1855
1856         }
1857     } else if (appData.useTelnet) {
1858         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1859
1860     } else {
1861         /* TCP socket interface differs somewhat between
1862            Unix and NT; handle details in the front end.
1863            */
1864         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1865     }
1866 }
1867
1868 void
1869 EscapeExpand (char *p, char *q)
1870 {       // [HGM] initstring: routine to shape up string arguments
1871         while(*p++ = *q++) if(p[-1] == '\\')
1872             switch(*q++) {
1873                 case 'n': p[-1] = '\n'; break;
1874                 case 'r': p[-1] = '\r'; break;
1875                 case 't': p[-1] = '\t'; break;
1876                 case '\\': p[-1] = '\\'; break;
1877                 case 0: *p = 0; return;
1878                 default: p[-1] = q[-1]; break;
1879             }
1880 }
1881
1882 void
1883 show_bytes (FILE *fp, char *buf, int count)
1884 {
1885     while (count--) {
1886         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1887             fprintf(fp, "\\%03o", *buf & 0xff);
1888         } else {
1889             putc(*buf, fp);
1890         }
1891         buf++;
1892     }
1893     fflush(fp);
1894 }
1895
1896 /* Returns an errno value */
1897 int
1898 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1899 {
1900     char buf[8192], *p, *q, *buflim;
1901     int left, newcount, outcount;
1902
1903     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1904         *appData.gateway != NULLCHAR) {
1905         if (appData.debugMode) {
1906             fprintf(debugFP, ">ICS: ");
1907             show_bytes(debugFP, message, count);
1908             fprintf(debugFP, "\n");
1909         }
1910         return OutputToProcess(pr, message, count, outError);
1911     }
1912
1913     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1914     p = message;
1915     q = buf;
1916     left = count;
1917     newcount = 0;
1918     while (left) {
1919         if (q >= buflim) {
1920             if (appData.debugMode) {
1921                 fprintf(debugFP, ">ICS: ");
1922                 show_bytes(debugFP, buf, newcount);
1923                 fprintf(debugFP, "\n");
1924             }
1925             outcount = OutputToProcess(pr, buf, newcount, outError);
1926             if (outcount < newcount) return -1; /* to be sure */
1927             q = buf;
1928             newcount = 0;
1929         }
1930         if (*p == '\n') {
1931             *q++ = '\r';
1932             newcount++;
1933         } else if (((unsigned char) *p) == TN_IAC) {
1934             *q++ = (char) TN_IAC;
1935             newcount ++;
1936         }
1937         *q++ = *p++;
1938         newcount++;
1939         left--;
1940     }
1941     if (appData.debugMode) {
1942         fprintf(debugFP, ">ICS: ");
1943         show_bytes(debugFP, buf, newcount);
1944         fprintf(debugFP, "\n");
1945     }
1946     outcount = OutputToProcess(pr, buf, newcount, outError);
1947     if (outcount < newcount) return -1; /* to be sure */
1948     return count;
1949 }
1950
1951 void
1952 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1953 {
1954     int outError, outCount;
1955     static int gotEof = 0;
1956     static FILE *ini;
1957
1958     /* Pass data read from player on to ICS */
1959     if (count > 0) {
1960         gotEof = 0;
1961         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1962         if (outCount < count) {
1963             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1964         }
1965         if(have_sent_ICS_logon == 2) {
1966           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1967             fprintf(ini, "%s", message);
1968             have_sent_ICS_logon = 3;
1969           } else
1970             have_sent_ICS_logon = 1;
1971         } else if(have_sent_ICS_logon == 3) {
1972             fprintf(ini, "%s", message);
1973             fclose(ini);
1974           have_sent_ICS_logon = 1;
1975         }
1976     } else if (count < 0) {
1977         RemoveInputSource(isr);
1978         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1979     } else if (gotEof++ > 0) {
1980         RemoveInputSource(isr);
1981         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1982     }
1983 }
1984
1985 void
1986 KeepAlive ()
1987 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1988     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1989     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1990     SendToICS("date\n");
1991     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1992 }
1993
1994 /* added routine for printf style output to ics */
1995 void
1996 ics_printf (char *format, ...)
1997 {
1998     char buffer[MSG_SIZ];
1999     va_list args;
2000
2001     va_start(args, format);
2002     vsnprintf(buffer, sizeof(buffer), format, args);
2003     buffer[sizeof(buffer)-1] = '\0';
2004     SendToICS(buffer);
2005     va_end(args);
2006 }
2007
2008 void
2009 SendToICS (char *s)
2010 {
2011     int count, outCount, outError;
2012
2013     if (icsPR == NoProc) return;
2014
2015     count = strlen(s);
2016     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2017     if (outCount < count) {
2018         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2019     }
2020 }
2021
2022 /* This is used for sending logon scripts to the ICS. Sending
2023    without a delay causes problems when using timestamp on ICC
2024    (at least on my machine). */
2025 void
2026 SendToICSDelayed (char *s, long msdelay)
2027 {
2028     int count, outCount, outError;
2029
2030     if (icsPR == NoProc) return;
2031
2032     count = strlen(s);
2033     if (appData.debugMode) {
2034         fprintf(debugFP, ">ICS: ");
2035         show_bytes(debugFP, s, count);
2036         fprintf(debugFP, "\n");
2037     }
2038     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2039                                       msdelay);
2040     if (outCount < count) {
2041         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2042     }
2043 }
2044
2045
2046 /* Remove all highlighting escape sequences in s
2047    Also deletes any suffix starting with '('
2048    */
2049 char *
2050 StripHighlightAndTitle (char *s)
2051 {
2052     static char retbuf[MSG_SIZ];
2053     char *p = retbuf;
2054
2055     while (*s != NULLCHAR) {
2056         while (*s == '\033') {
2057             while (*s != NULLCHAR && !isalpha(*s)) s++;
2058             if (*s != NULLCHAR) s++;
2059         }
2060         while (*s != NULLCHAR && *s != '\033') {
2061             if (*s == '(' || *s == '[') {
2062                 *p = NULLCHAR;
2063                 return retbuf;
2064             }
2065             *p++ = *s++;
2066         }
2067     }
2068     *p = NULLCHAR;
2069     return retbuf;
2070 }
2071
2072 /* Remove all highlighting escape sequences in s */
2073 char *
2074 StripHighlight (char *s)
2075 {
2076     static char retbuf[MSG_SIZ];
2077     char *p = retbuf;
2078
2079     while (*s != NULLCHAR) {
2080         while (*s == '\033') {
2081             while (*s != NULLCHAR && !isalpha(*s)) s++;
2082             if (*s != NULLCHAR) s++;
2083         }
2084         while (*s != NULLCHAR && *s != '\033') {
2085             *p++ = *s++;
2086         }
2087     }
2088     *p = NULLCHAR;
2089     return retbuf;
2090 }
2091
2092 char engineVariant[MSG_SIZ];
2093 char *variantNames[] = VARIANT_NAMES;
2094 char *
2095 VariantName (VariantClass v)
2096 {
2097     if(v == VariantUnknown || *engineVariant) return engineVariant;
2098     return variantNames[v];
2099 }
2100
2101
2102 /* Identify a variant from the strings the chess servers use or the
2103    PGN Variant tag names we use. */
2104 VariantClass
2105 StringToVariant (char *e)
2106 {
2107     char *p;
2108     int wnum = -1;
2109     VariantClass v = VariantNormal;
2110     int i, found = FALSE;
2111     char buf[MSG_SIZ], c;
2112     int len;
2113
2114     if (!e) return v;
2115
2116     /* [HGM] skip over optional board-size prefixes */
2117     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2118         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2119         while( *e++ != '_');
2120     }
2121
2122     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2123         v = VariantNormal;
2124         found = TRUE;
2125     } else
2126     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2127       if (p = StrCaseStr(e, variantNames[i])) {
2128         if(p && i >= VariantShogi && (p != e || isalpha(p[strlen(variantNames[i])]))) continue;
2129         v = (VariantClass) i;
2130         found = TRUE;
2131         break;
2132       }
2133     }
2134
2135     if (!found) {
2136       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2137           || StrCaseStr(e, "wild/fr")
2138           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2139         v = VariantFischeRandom;
2140       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2141                  (i = 1, p = StrCaseStr(e, "w"))) {
2142         p += i;
2143         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2144         if (isdigit(*p)) {
2145           wnum = atoi(p);
2146         } else {
2147           wnum = -1;
2148         }
2149         switch (wnum) {
2150         case 0: /* FICS only, actually */
2151         case 1:
2152           /* Castling legal even if K starts on d-file */
2153           v = VariantWildCastle;
2154           break;
2155         case 2:
2156         case 3:
2157         case 4:
2158           /* Castling illegal even if K & R happen to start in
2159              normal positions. */
2160           v = VariantNoCastle;
2161           break;
2162         case 5:
2163         case 7:
2164         case 8:
2165         case 10:
2166         case 11:
2167         case 12:
2168         case 13:
2169         case 14:
2170         case 15:
2171         case 18:
2172         case 19:
2173           /* Castling legal iff K & R start in normal positions */
2174           v = VariantNormal;
2175           break;
2176         case 6:
2177         case 20:
2178         case 21:
2179           /* Special wilds for position setup; unclear what to do here */
2180           v = VariantLoadable;
2181           break;
2182         case 9:
2183           /* Bizarre ICC game */
2184           v = VariantTwoKings;
2185           break;
2186         case 16:
2187           v = VariantKriegspiel;
2188           break;
2189         case 17:
2190           v = VariantLosers;
2191           break;
2192         case 22:
2193           v = VariantFischeRandom;
2194           break;
2195         case 23:
2196           v = VariantCrazyhouse;
2197           break;
2198         case 24:
2199           v = VariantBughouse;
2200           break;
2201         case 25:
2202           v = Variant3Check;
2203           break;
2204         case 26:
2205           /* Not quite the same as FICS suicide! */
2206           v = VariantGiveaway;
2207           break;
2208         case 27:
2209           v = VariantAtomic;
2210           break;
2211         case 28:
2212           v = VariantShatranj;
2213           break;
2214
2215         /* Temporary names for future ICC types.  The name *will* change in
2216            the next xboard/WinBoard release after ICC defines it. */
2217         case 29:
2218           v = Variant29;
2219           break;
2220         case 30:
2221           v = Variant30;
2222           break;
2223         case 31:
2224           v = Variant31;
2225           break;
2226         case 32:
2227           v = Variant32;
2228           break;
2229         case 33:
2230           v = Variant33;
2231           break;
2232         case 34:
2233           v = Variant34;
2234           break;
2235         case 35:
2236           v = Variant35;
2237           break;
2238         case 36:
2239           v = Variant36;
2240           break;
2241         case 37:
2242           v = VariantShogi;
2243           break;
2244         case 38:
2245           v = VariantXiangqi;
2246           break;
2247         case 39:
2248           v = VariantCourier;
2249           break;
2250         case 40:
2251           v = VariantGothic;
2252           break;
2253         case 41:
2254           v = VariantCapablanca;
2255           break;
2256         case 42:
2257           v = VariantKnightmate;
2258           break;
2259         case 43:
2260           v = VariantFairy;
2261           break;
2262         case 44:
2263           v = VariantCylinder;
2264           break;
2265         case 45:
2266           v = VariantFalcon;
2267           break;
2268         case 46:
2269           v = VariantCapaRandom;
2270           break;
2271         case 47:
2272           v = VariantBerolina;
2273           break;
2274         case 48:
2275           v = VariantJanus;
2276           break;
2277         case 49:
2278           v = VariantSuper;
2279           break;
2280         case 50:
2281           v = VariantGreat;
2282           break;
2283         case -1:
2284           /* Found "wild" or "w" in the string but no number;
2285              must assume it's normal chess. */
2286           v = VariantNormal;
2287           break;
2288         default:
2289           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2290           if( (len >= MSG_SIZ) && appData.debugMode )
2291             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2292
2293           DisplayError(buf, 0);
2294           v = VariantUnknown;
2295           break;
2296         }
2297       }
2298     }
2299     if (appData.debugMode) {
2300       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2301               e, wnum, VariantName(v));
2302     }
2303     return v;
2304 }
2305
2306 static int leftover_start = 0, leftover_len = 0;
2307 char star_match[STAR_MATCH_N][MSG_SIZ];
2308
2309 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2310    advance *index beyond it, and set leftover_start to the new value of
2311    *index; else return FALSE.  If pattern contains the character '*', it
2312    matches any sequence of characters not containing '\r', '\n', or the
2313    character following the '*' (if any), and the matched sequence(s) are
2314    copied into star_match.
2315    */
2316 int
2317 looking_at ( char *buf, int *index, char *pattern)
2318 {
2319     char *bufp = &buf[*index], *patternp = pattern;
2320     int star_count = 0;
2321     char *matchp = star_match[0];
2322
2323     for (;;) {
2324         if (*patternp == NULLCHAR) {
2325             *index = leftover_start = bufp - buf;
2326             *matchp = NULLCHAR;
2327             return TRUE;
2328         }
2329         if (*bufp == NULLCHAR) return FALSE;
2330         if (*patternp == '*') {
2331             if (*bufp == *(patternp + 1)) {
2332                 *matchp = NULLCHAR;
2333                 matchp = star_match[++star_count];
2334                 patternp += 2;
2335                 bufp++;
2336                 continue;
2337             } else if (*bufp == '\n' || *bufp == '\r') {
2338                 patternp++;
2339                 if (*patternp == NULLCHAR)
2340                   continue;
2341                 else
2342                   return FALSE;
2343             } else {
2344                 *matchp++ = *bufp++;
2345                 continue;
2346             }
2347         }
2348         if (*patternp != *bufp) return FALSE;
2349         patternp++;
2350         bufp++;
2351     }
2352 }
2353
2354 void
2355 SendToPlayer (char *data, int length)
2356 {
2357     int error, outCount;
2358     outCount = OutputToProcess(NoProc, data, length, &error);
2359     if (outCount < length) {
2360         DisplayFatalError(_("Error writing to display"), error, 1);
2361     }
2362 }
2363
2364 void
2365 PackHolding (char packed[], char *holding)
2366 {
2367     char *p = holding;
2368     char *q = packed;
2369     int runlength = 0;
2370     int curr = 9999;
2371     do {
2372         if (*p == curr) {
2373             runlength++;
2374         } else {
2375             switch (runlength) {
2376               case 0:
2377                 break;
2378               case 1:
2379                 *q++ = curr;
2380                 break;
2381               case 2:
2382                 *q++ = curr;
2383                 *q++ = curr;
2384                 break;
2385               default:
2386                 sprintf(q, "%d", runlength);
2387                 while (*q) q++;
2388                 *q++ = curr;
2389                 break;
2390             }
2391             runlength = 1;
2392             curr = *p;
2393         }
2394     } while (*p++);
2395     *q = NULLCHAR;
2396 }
2397
2398 /* Telnet protocol requests from the front end */
2399 void
2400 TelnetRequest (unsigned char ddww, unsigned char option)
2401 {
2402     unsigned char msg[3];
2403     int outCount, outError;
2404
2405     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2406
2407     if (appData.debugMode) {
2408         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2409         switch (ddww) {
2410           case TN_DO:
2411             ddwwStr = "DO";
2412             break;
2413           case TN_DONT:
2414             ddwwStr = "DONT";
2415             break;
2416           case TN_WILL:
2417             ddwwStr = "WILL";
2418             break;
2419           case TN_WONT:
2420             ddwwStr = "WONT";
2421             break;
2422           default:
2423             ddwwStr = buf1;
2424             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2425             break;
2426         }
2427         switch (option) {
2428           case TN_ECHO:
2429             optionStr = "ECHO";
2430             break;
2431           default:
2432             optionStr = buf2;
2433             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2434             break;
2435         }
2436         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2437     }
2438     msg[0] = TN_IAC;
2439     msg[1] = ddww;
2440     msg[2] = option;
2441     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2442     if (outCount < 3) {
2443         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2444     }
2445 }
2446
2447 void
2448 DoEcho ()
2449 {
2450     if (!appData.icsActive) return;
2451     TelnetRequest(TN_DO, TN_ECHO);
2452 }
2453
2454 void
2455 DontEcho ()
2456 {
2457     if (!appData.icsActive) return;
2458     TelnetRequest(TN_DONT, TN_ECHO);
2459 }
2460
2461 void
2462 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2463 {
2464     /* put the holdings sent to us by the server on the board holdings area */
2465     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2466     char p;
2467     ChessSquare piece;
2468
2469     if(gameInfo.holdingsWidth < 2)  return;
2470     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2471         return; // prevent overwriting by pre-board holdings
2472
2473     if( (int)lowestPiece >= BlackPawn ) {
2474         holdingsColumn = 0;
2475         countsColumn = 1;
2476         holdingsStartRow = BOARD_HEIGHT-1;
2477         direction = -1;
2478     } else {
2479         holdingsColumn = BOARD_WIDTH-1;
2480         countsColumn = BOARD_WIDTH-2;
2481         holdingsStartRow = 0;
2482         direction = 1;
2483     }
2484
2485     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2486         board[i][holdingsColumn] = EmptySquare;
2487         board[i][countsColumn]   = (ChessSquare) 0;
2488     }
2489     while( (p=*holdings++) != NULLCHAR ) {
2490         piece = CharToPiece( ToUpper(p) );
2491         if(piece == EmptySquare) continue;
2492         /*j = (int) piece - (int) WhitePawn;*/
2493         j = PieceToNumber(piece);
2494         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2495         if(j < 0) continue;               /* should not happen */
2496         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2497         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2498         board[holdingsStartRow+j*direction][countsColumn]++;
2499     }
2500 }
2501
2502
2503 void
2504 VariantSwitch (Board board, VariantClass newVariant)
2505 {
2506    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2507    static Board oldBoard;
2508
2509    startedFromPositionFile = FALSE;
2510    if(gameInfo.variant == newVariant) return;
2511
2512    /* [HGM] This routine is called each time an assignment is made to
2513     * gameInfo.variant during a game, to make sure the board sizes
2514     * are set to match the new variant. If that means adding or deleting
2515     * holdings, we shift the playing board accordingly
2516     * This kludge is needed because in ICS observe mode, we get boards
2517     * of an ongoing game without knowing the variant, and learn about the
2518     * latter only later. This can be because of the move list we requested,
2519     * in which case the game history is refilled from the beginning anyway,
2520     * but also when receiving holdings of a crazyhouse game. In the latter
2521     * case we want to add those holdings to the already received position.
2522     */
2523
2524
2525    if (appData.debugMode) {
2526      fprintf(debugFP, "Switch board from %s to %s\n",
2527              VariantName(gameInfo.variant), VariantName(newVariant));
2528      setbuf(debugFP, NULL);
2529    }
2530    shuffleOpenings = 0;       /* [HGM] shuffle */
2531    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2532    switch(newVariant)
2533      {
2534      case VariantShogi:
2535        newWidth = 9;  newHeight = 9;
2536        gameInfo.holdingsSize = 7;
2537      case VariantBughouse:
2538      case VariantCrazyhouse:
2539        newHoldingsWidth = 2; break;
2540      case VariantGreat:
2541        newWidth = 10;
2542      case VariantSuper:
2543        newHoldingsWidth = 2;
2544        gameInfo.holdingsSize = 8;
2545        break;
2546      case VariantGothic:
2547      case VariantCapablanca:
2548      case VariantCapaRandom:
2549        newWidth = 10;
2550      default:
2551        newHoldingsWidth = gameInfo.holdingsSize = 0;
2552      };
2553
2554    if(newWidth  != gameInfo.boardWidth  ||
2555       newHeight != gameInfo.boardHeight ||
2556       newHoldingsWidth != gameInfo.holdingsWidth ) {
2557
2558      /* shift position to new playing area, if needed */
2559      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2560        for(i=0; i<BOARD_HEIGHT; i++)
2561          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2562            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2563              board[i][j];
2564        for(i=0; i<newHeight; i++) {
2565          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2566          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2567        }
2568      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2569        for(i=0; i<BOARD_HEIGHT; i++)
2570          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2571            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2572              board[i][j];
2573      }
2574      board[HOLDINGS_SET] = 0;
2575      gameInfo.boardWidth  = newWidth;
2576      gameInfo.boardHeight = newHeight;
2577      gameInfo.holdingsWidth = newHoldingsWidth;
2578      gameInfo.variant = newVariant;
2579      InitDrawingSizes(-2, 0);
2580    } else gameInfo.variant = newVariant;
2581    CopyBoard(oldBoard, board);   // remember correctly formatted board
2582      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2583    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2584 }
2585
2586 static int loggedOn = FALSE;
2587
2588 /*-- Game start info cache: --*/
2589 int gs_gamenum;
2590 char gs_kind[MSG_SIZ];
2591 static char player1Name[128] = "";
2592 static char player2Name[128] = "";
2593 static char cont_seq[] = "\n\\   ";
2594 static int player1Rating = -1;
2595 static int player2Rating = -1;
2596 /*----------------------------*/
2597
2598 ColorClass curColor = ColorNormal;
2599 int suppressKibitz = 0;
2600
2601 // [HGM] seekgraph
2602 Boolean soughtPending = FALSE;
2603 Boolean seekGraphUp;
2604 #define MAX_SEEK_ADS 200
2605 #define SQUARE 0x80
2606 char *seekAdList[MAX_SEEK_ADS];
2607 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2608 float tcList[MAX_SEEK_ADS];
2609 char colorList[MAX_SEEK_ADS];
2610 int nrOfSeekAds = 0;
2611 int minRating = 1010, maxRating = 2800;
2612 int hMargin = 10, vMargin = 20, h, w;
2613 extern int squareSize, lineGap;
2614
2615 void
2616 PlotSeekAd (int i)
2617 {
2618         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2619         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2620         if(r < minRating+100 && r >=0 ) r = minRating+100;
2621         if(r > maxRating) r = maxRating;
2622         if(tc < 1.f) tc = 1.f;
2623         if(tc > 95.f) tc = 95.f;
2624         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2625         y = ((double)r - minRating)/(maxRating - minRating)
2626             * (h-vMargin-squareSize/8-1) + vMargin;
2627         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2628         if(strstr(seekAdList[i], " u ")) color = 1;
2629         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2630            !strstr(seekAdList[i], "bullet") &&
2631            !strstr(seekAdList[i], "blitz") &&
2632            !strstr(seekAdList[i], "standard") ) color = 2;
2633         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2634         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2635 }
2636
2637 void
2638 PlotSingleSeekAd (int i)
2639 {
2640         PlotSeekAd(i);
2641 }
2642
2643 void
2644 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2645 {
2646         char buf[MSG_SIZ], *ext = "";
2647         VariantClass v = StringToVariant(type);
2648         if(strstr(type, "wild")) {
2649             ext = type + 4; // append wild number
2650             if(v == VariantFischeRandom) type = "chess960"; else
2651             if(v == VariantLoadable) type = "setup"; else
2652             type = VariantName(v);
2653         }
2654         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2655         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2656             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2657             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2658             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2659             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2660             seekNrList[nrOfSeekAds] = nr;
2661             zList[nrOfSeekAds] = 0;
2662             seekAdList[nrOfSeekAds++] = StrSave(buf);
2663             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2664         }
2665 }
2666
2667 void
2668 EraseSeekDot (int i)
2669 {
2670     int x = xList[i], y = yList[i], d=squareSize/4, k;
2671     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2672     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2673     // now replot every dot that overlapped
2674     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2675         int xx = xList[k], yy = yList[k];
2676         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2677             DrawSeekDot(xx, yy, colorList[k]);
2678     }
2679 }
2680
2681 void
2682 RemoveSeekAd (int nr)
2683 {
2684         int i;
2685         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2686             EraseSeekDot(i);
2687             if(seekAdList[i]) free(seekAdList[i]);
2688             seekAdList[i] = seekAdList[--nrOfSeekAds];
2689             seekNrList[i] = seekNrList[nrOfSeekAds];
2690             ratingList[i] = ratingList[nrOfSeekAds];
2691             colorList[i]  = colorList[nrOfSeekAds];
2692             tcList[i] = tcList[nrOfSeekAds];
2693             xList[i]  = xList[nrOfSeekAds];
2694             yList[i]  = yList[nrOfSeekAds];
2695             zList[i]  = zList[nrOfSeekAds];
2696             seekAdList[nrOfSeekAds] = NULL;
2697             break;
2698         }
2699 }
2700
2701 Boolean
2702 MatchSoughtLine (char *line)
2703 {
2704     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2705     int nr, base, inc, u=0; char dummy;
2706
2707     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2708        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2709        (u=1) &&
2710        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2711         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2712         // match: compact and save the line
2713         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2714         return TRUE;
2715     }
2716     return FALSE;
2717 }
2718
2719 int
2720 DrawSeekGraph ()
2721 {
2722     int i;
2723     if(!seekGraphUp) return FALSE;
2724     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2725     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2726
2727     DrawSeekBackground(0, 0, w, h);
2728     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2729     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2730     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2731         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2732         yy = h-1-yy;
2733         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2734         if(i%500 == 0) {
2735             char buf[MSG_SIZ];
2736             snprintf(buf, MSG_SIZ, "%d", i);
2737             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2738         }
2739     }
2740     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2741     for(i=1; i<100; i+=(i<10?1:5)) {
2742         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2743         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2744         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2745             char buf[MSG_SIZ];
2746             snprintf(buf, MSG_SIZ, "%d", i);
2747             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2748         }
2749     }
2750     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2751     return TRUE;
2752 }
2753
2754 int
2755 SeekGraphClick (ClickType click, int x, int y, int moving)
2756 {
2757     static int lastDown = 0, displayed = 0, lastSecond;
2758     if(y < 0) return FALSE;
2759     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2760         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2761         if(!seekGraphUp) return FALSE;
2762         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2763         DrawPosition(TRUE, NULL);
2764         return TRUE;
2765     }
2766     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2767         if(click == Release || moving) return FALSE;
2768         nrOfSeekAds = 0;
2769         soughtPending = TRUE;
2770         SendToICS(ics_prefix);
2771         SendToICS("sought\n"); // should this be "sought all"?
2772     } else { // issue challenge based on clicked ad
2773         int dist = 10000; int i, closest = 0, second = 0;
2774         for(i=0; i<nrOfSeekAds; i++) {
2775             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2776             if(d < dist) { dist = d; closest = i; }
2777             second += (d - zList[i] < 120); // count in-range ads
2778             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2779         }
2780         if(dist < 120) {
2781             char buf[MSG_SIZ];
2782             second = (second > 1);
2783             if(displayed != closest || second != lastSecond) {
2784                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2785                 lastSecond = second; displayed = closest;
2786             }
2787             if(click == Press) {
2788                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2789                 lastDown = closest;
2790                 return TRUE;
2791             } // on press 'hit', only show info
2792             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2793             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2794             SendToICS(ics_prefix);
2795             SendToICS(buf);
2796             return TRUE; // let incoming board of started game pop down the graph
2797         } else if(click == Release) { // release 'miss' is ignored
2798             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2799             if(moving == 2) { // right up-click
2800                 nrOfSeekAds = 0; // refresh graph
2801                 soughtPending = TRUE;
2802                 SendToICS(ics_prefix);
2803                 SendToICS("sought\n"); // should this be "sought all"?
2804             }
2805             return TRUE;
2806         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2807         // press miss or release hit 'pop down' seek graph
2808         seekGraphUp = FALSE;
2809         DrawPosition(TRUE, NULL);
2810     }
2811     return TRUE;
2812 }
2813
2814 void
2815 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2816 {
2817 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2818 #define STARTED_NONE 0
2819 #define STARTED_MOVES 1
2820 #define STARTED_BOARD 2
2821 #define STARTED_OBSERVE 3
2822 #define STARTED_HOLDINGS 4
2823 #define STARTED_CHATTER 5
2824 #define STARTED_COMMENT 6
2825 #define STARTED_MOVES_NOHIDE 7
2826
2827     static int started = STARTED_NONE;
2828     static char parse[20000];
2829     static int parse_pos = 0;
2830     static char buf[BUF_SIZE + 1];
2831     static int firstTime = TRUE, intfSet = FALSE;
2832     static ColorClass prevColor = ColorNormal;
2833     static int savingComment = FALSE;
2834     static int cmatch = 0; // continuation sequence match
2835     char *bp;
2836     char str[MSG_SIZ];
2837     int i, oldi;
2838     int buf_len;
2839     int next_out;
2840     int tkind;
2841     int backup;    /* [DM] For zippy color lines */
2842     char *p;
2843     char talker[MSG_SIZ]; // [HGM] chat
2844     int channel, collective=0;
2845
2846     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2847
2848     if (appData.debugMode) {
2849       if (!error) {
2850         fprintf(debugFP, "<ICS: ");
2851         show_bytes(debugFP, data, count);
2852         fprintf(debugFP, "\n");
2853       }
2854     }
2855
2856     if (appData.debugMode) { int f = forwardMostMove;
2857         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2858                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2859                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2860     }
2861     if (count > 0) {
2862         /* If last read ended with a partial line that we couldn't parse,
2863            prepend it to the new read and try again. */
2864         if (leftover_len > 0) {
2865             for (i=0; i<leftover_len; i++)
2866               buf[i] = buf[leftover_start + i];
2867         }
2868
2869     /* copy new characters into the buffer */
2870     bp = buf + leftover_len;
2871     buf_len=leftover_len;
2872     for (i=0; i<count; i++)
2873     {
2874         // ignore these
2875         if (data[i] == '\r')
2876             continue;
2877
2878         // join lines split by ICS?
2879         if (!appData.noJoin)
2880         {
2881             /*
2882                 Joining just consists of finding matches against the
2883                 continuation sequence, and discarding that sequence
2884                 if found instead of copying it.  So, until a match
2885                 fails, there's nothing to do since it might be the
2886                 complete sequence, and thus, something we don't want
2887                 copied.
2888             */
2889             if (data[i] == cont_seq[cmatch])
2890             {
2891                 cmatch++;
2892                 if (cmatch == strlen(cont_seq))
2893                 {
2894                     cmatch = 0; // complete match.  just reset the counter
2895
2896                     /*
2897                         it's possible for the ICS to not include the space
2898                         at the end of the last word, making our [correct]
2899                         join operation fuse two separate words.  the server
2900                         does this when the space occurs at the width setting.
2901                     */
2902                     if (!buf_len || buf[buf_len-1] != ' ')
2903                     {
2904                         *bp++ = ' ';
2905                         buf_len++;
2906                     }
2907                 }
2908                 continue;
2909             }
2910             else if (cmatch)
2911             {
2912                 /*
2913                     match failed, so we have to copy what matched before
2914                     falling through and copying this character.  In reality,
2915                     this will only ever be just the newline character, but
2916                     it doesn't hurt to be precise.
2917                 */
2918                 strncpy(bp, cont_seq, cmatch);
2919                 bp += cmatch;
2920                 buf_len += cmatch;
2921                 cmatch = 0;
2922             }
2923         }
2924
2925         // copy this char
2926         *bp++ = data[i];
2927         buf_len++;
2928     }
2929
2930         buf[buf_len] = NULLCHAR;
2931 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2932         next_out = 0;
2933         leftover_start = 0;
2934
2935         i = 0;
2936         while (i < buf_len) {
2937             /* Deal with part of the TELNET option negotiation
2938                protocol.  We refuse to do anything beyond the
2939                defaults, except that we allow the WILL ECHO option,
2940                which ICS uses to turn off password echoing when we are
2941                directly connected to it.  We reject this option
2942                if localLineEditing mode is on (always on in xboard)
2943                and we are talking to port 23, which might be a real
2944                telnet server that will try to keep WILL ECHO on permanently.
2945              */
2946             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2947                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2948                 unsigned char option;
2949                 oldi = i;
2950                 switch ((unsigned char) buf[++i]) {
2951                   case TN_WILL:
2952                     if (appData.debugMode)
2953                       fprintf(debugFP, "\n<WILL ");
2954                     switch (option = (unsigned char) buf[++i]) {
2955                       case TN_ECHO:
2956                         if (appData.debugMode)
2957                           fprintf(debugFP, "ECHO ");
2958                         /* Reply only if this is a change, according
2959                            to the protocol rules. */
2960                         if (remoteEchoOption) break;
2961                         if (appData.localLineEditing &&
2962                             atoi(appData.icsPort) == TN_PORT) {
2963                             TelnetRequest(TN_DONT, TN_ECHO);
2964                         } else {
2965                             EchoOff();
2966                             TelnetRequest(TN_DO, TN_ECHO);
2967                             remoteEchoOption = TRUE;
2968                         }
2969                         break;
2970                       default:
2971                         if (appData.debugMode)
2972                           fprintf(debugFP, "%d ", option);
2973                         /* Whatever this is, we don't want it. */
2974                         TelnetRequest(TN_DONT, option);
2975                         break;
2976                     }
2977                     break;
2978                   case TN_WONT:
2979                     if (appData.debugMode)
2980                       fprintf(debugFP, "\n<WONT ");
2981                     switch (option = (unsigned char) buf[++i]) {
2982                       case TN_ECHO:
2983                         if (appData.debugMode)
2984                           fprintf(debugFP, "ECHO ");
2985                         /* Reply only if this is a change, according
2986                            to the protocol rules. */
2987                         if (!remoteEchoOption) break;
2988                         EchoOn();
2989                         TelnetRequest(TN_DONT, TN_ECHO);
2990                         remoteEchoOption = FALSE;
2991                         break;
2992                       default:
2993                         if (appData.debugMode)
2994                           fprintf(debugFP, "%d ", (unsigned char) option);
2995                         /* Whatever this is, it must already be turned
2996                            off, because we never agree to turn on
2997                            anything non-default, so according to the
2998                            protocol rules, we don't reply. */
2999                         break;
3000                     }
3001                     break;
3002                   case TN_DO:
3003                     if (appData.debugMode)
3004                       fprintf(debugFP, "\n<DO ");
3005                     switch (option = (unsigned char) buf[++i]) {
3006                       default:
3007                         /* Whatever this is, we refuse to do it. */
3008                         if (appData.debugMode)
3009                           fprintf(debugFP, "%d ", option);
3010                         TelnetRequest(TN_WONT, option);
3011                         break;
3012                     }
3013                     break;
3014                   case TN_DONT:
3015                     if (appData.debugMode)
3016                       fprintf(debugFP, "\n<DONT ");
3017                     switch (option = (unsigned char) buf[++i]) {
3018                       default:
3019                         if (appData.debugMode)
3020                           fprintf(debugFP, "%d ", option);
3021                         /* Whatever this is, we are already not doing
3022                            it, because we never agree to do anything
3023                            non-default, so according to the protocol
3024                            rules, we don't reply. */
3025                         break;
3026                     }
3027                     break;
3028                   case TN_IAC:
3029                     if (appData.debugMode)
3030                       fprintf(debugFP, "\n<IAC ");
3031                     /* Doubled IAC; pass it through */
3032                     i--;
3033                     break;
3034                   default:
3035                     if (appData.debugMode)
3036                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3037                     /* Drop all other telnet commands on the floor */
3038                     break;
3039                 }
3040                 if (oldi > next_out)
3041                   SendToPlayer(&buf[next_out], oldi - next_out);
3042                 if (++i > next_out)
3043                   next_out = i;
3044                 continue;
3045             }
3046
3047             /* OK, this at least will *usually* work */
3048             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3049                 loggedOn = TRUE;
3050             }
3051
3052             if (loggedOn && !intfSet) {
3053                 if (ics_type == ICS_ICC) {
3054                   snprintf(str, MSG_SIZ,
3055                           "/set-quietly interface %s\n/set-quietly style 12\n",
3056                           programVersion);
3057                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3058                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3059                 } else if (ics_type == ICS_CHESSNET) {
3060                   snprintf(str, MSG_SIZ, "/style 12\n");
3061                 } else {
3062                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3063                   strcat(str, programVersion);
3064                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3065                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3066                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3067 #ifdef WIN32
3068                   strcat(str, "$iset nohighlight 1\n");
3069 #endif
3070                   strcat(str, "$iset lock 1\n$style 12\n");
3071                 }
3072                 SendToICS(str);
3073                 NotifyFrontendLogin();
3074                 intfSet = TRUE;
3075             }
3076
3077             if (started == STARTED_COMMENT) {
3078                 /* Accumulate characters in comment */
3079                 parse[parse_pos++] = buf[i];
3080                 if (buf[i] == '\n') {
3081                     parse[parse_pos] = NULLCHAR;
3082                     if(chattingPartner>=0) {
3083                         char mess[MSG_SIZ];
3084                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3085                         OutputChatMessage(chattingPartner, mess);
3086                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3087                             int p;
3088                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3089                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3090                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3091                                 OutputChatMessage(p, mess);
3092                                 break;
3093                             }
3094                         }
3095                         chattingPartner = -1;
3096                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3097                         collective = 0;
3098                     } else
3099                     if(!suppressKibitz) // [HGM] kibitz
3100                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3101                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3102                         int nrDigit = 0, nrAlph = 0, j;
3103                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3104                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3105                         parse[parse_pos] = NULLCHAR;
3106                         // try to be smart: if it does not look like search info, it should go to
3107                         // ICS interaction window after all, not to engine-output window.
3108                         for(j=0; j<parse_pos; j++) { // count letters and digits
3109                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3110                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3111                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3112                         }
3113                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3114                             int depth=0; float score;
3115                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3116                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3117                                 pvInfoList[forwardMostMove-1].depth = depth;
3118                                 pvInfoList[forwardMostMove-1].score = 100*score;
3119                             }
3120                             OutputKibitz(suppressKibitz, parse);
3121                         } else {
3122                             char tmp[MSG_SIZ];
3123                             if(gameMode == IcsObserving) // restore original ICS messages
3124                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3125                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3126                             else
3127                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3128                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3129                             SendToPlayer(tmp, strlen(tmp));
3130                         }
3131                         next_out = i+1; // [HGM] suppress printing in ICS window
3132                     }
3133                     started = STARTED_NONE;
3134                 } else {
3135                     /* Don't match patterns against characters in comment */
3136                     i++;
3137                     continue;
3138                 }
3139             }
3140             if (started == STARTED_CHATTER) {
3141                 if (buf[i] != '\n') {
3142                     /* Don't match patterns against characters in chatter */
3143                     i++;
3144                     continue;
3145                 }
3146                 started = STARTED_NONE;
3147                 if(suppressKibitz) next_out = i+1;
3148             }
3149
3150             /* Kludge to deal with rcmd protocol */
3151             if (firstTime && looking_at(buf, &i, "\001*")) {
3152                 DisplayFatalError(&buf[1], 0, 1);
3153                 continue;
3154             } else {
3155                 firstTime = FALSE;
3156             }
3157
3158             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3159                 ics_type = ICS_ICC;
3160                 ics_prefix = "/";
3161                 if (appData.debugMode)
3162                   fprintf(debugFP, "ics_type %d\n", ics_type);
3163                 continue;
3164             }
3165             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3166                 ics_type = ICS_FICS;
3167                 ics_prefix = "$";
3168                 if (appData.debugMode)
3169                   fprintf(debugFP, "ics_type %d\n", ics_type);
3170                 continue;
3171             }
3172             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3173                 ics_type = ICS_CHESSNET;
3174                 ics_prefix = "/";
3175                 if (appData.debugMode)
3176                   fprintf(debugFP, "ics_type %d\n", ics_type);
3177                 continue;
3178             }
3179
3180             if (!loggedOn &&
3181                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3182                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3183                  looking_at(buf, &i, "will be \"*\""))) {
3184               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3185               continue;
3186             }
3187
3188             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3189               char buf[MSG_SIZ];
3190               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3191               DisplayIcsInteractionTitle(buf);
3192               have_set_title = TRUE;
3193             }
3194
3195             /* skip finger notes */
3196             if (started == STARTED_NONE &&
3197                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3198                  (buf[i] == '1' && buf[i+1] == '0')) &&
3199                 buf[i+2] == ':' && buf[i+3] == ' ') {
3200               started = STARTED_CHATTER;
3201               i += 3;
3202               continue;
3203             }
3204
3205             oldi = i;
3206             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3207             if(appData.seekGraph) {
3208                 if(soughtPending && MatchSoughtLine(buf+i)) {
3209                     i = strstr(buf+i, "rated") - buf;
3210                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3211                     next_out = leftover_start = i;
3212                     started = STARTED_CHATTER;
3213                     suppressKibitz = TRUE;
3214                     continue;
3215                 }
3216                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3217                         && looking_at(buf, &i, "* ads displayed")) {
3218                     soughtPending = FALSE;
3219                     seekGraphUp = TRUE;
3220                     DrawSeekGraph();
3221                     continue;
3222                 }
3223                 if(appData.autoRefresh) {
3224                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3225                         int s = (ics_type == ICS_ICC); // ICC format differs
3226                         if(seekGraphUp)
3227                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3228                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3229                         looking_at(buf, &i, "*% "); // eat prompt
3230                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3231                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3232                         next_out = i; // suppress
3233                         continue;
3234                     }
3235                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3236                         char *p = star_match[0];
3237                         while(*p) {
3238                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3239                             while(*p && *p++ != ' '); // next
3240                         }
3241                         looking_at(buf, &i, "*% "); // eat prompt
3242                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3243                         next_out = i;
3244                         continue;
3245                     }
3246                 }
3247             }
3248
3249             /* skip formula vars */
3250             if (started == STARTED_NONE &&
3251                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3252               started = STARTED_CHATTER;
3253               i += 3;
3254               continue;
3255             }
3256
3257             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3258             if (appData.autoKibitz && started == STARTED_NONE &&
3259                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3260                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3261                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3262                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3263                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3264                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3265                         suppressKibitz = TRUE;
3266                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3267                         next_out = i;
3268                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3269                                 && (gameMode == IcsPlayingWhite)) ||
3270                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3271                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3272                             started = STARTED_CHATTER; // own kibitz we simply discard
3273                         else {
3274                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3275                             parse_pos = 0; parse[0] = NULLCHAR;
3276                             savingComment = TRUE;
3277                             suppressKibitz = gameMode != IcsObserving ? 2 :
3278                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3279                         }
3280                         continue;
3281                 } else
3282                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3283                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3284                          && atoi(star_match[0])) {
3285                     // suppress the acknowledgements of our own autoKibitz
3286                     char *p;
3287                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3288                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3289                     SendToPlayer(star_match[0], strlen(star_match[0]));
3290                     if(looking_at(buf, &i, "*% ")) // eat prompt
3291                         suppressKibitz = FALSE;
3292                     next_out = i;
3293                     continue;
3294                 }
3295             } // [HGM] kibitz: end of patch
3296
3297             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3298
3299             // [HGM] chat: intercept tells by users for which we have an open chat window
3300             channel = -1;
3301             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3302                                            looking_at(buf, &i, "* whispers:") ||
3303                                            looking_at(buf, &i, "* kibitzes:") ||
3304                                            looking_at(buf, &i, "* shouts:") ||
3305                                            looking_at(buf, &i, "* c-shouts:") ||
3306                                            looking_at(buf, &i, "--> * ") ||
3307                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3308                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3309                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3310                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3311                 int p;
3312                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3313                 chattingPartner = -1; collective = 0;
3314
3315                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3316                 for(p=0; p<MAX_CHAT; p++) {
3317                     collective = 1;
3318                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3319                     talker[0] = '['; strcat(talker, "] ");
3320                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3321                     chattingPartner = p; break;
3322                     }
3323                 } else
3324                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3325                 for(p=0; p<MAX_CHAT; p++) {
3326                     collective = 1;
3327                     if(!strcmp("kibitzes", chatPartner[p])) {
3328                         talker[0] = '['; strcat(talker, "] ");
3329                         chattingPartner = p; break;
3330                     }
3331                 } else
3332                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3333                 for(p=0; p<MAX_CHAT; p++) {
3334                     collective = 1;
3335                     if(!strcmp("whispers", chatPartner[p])) {
3336                         talker[0] = '['; strcat(talker, "] ");
3337                         chattingPartner = p; break;
3338                     }
3339                 } else
3340                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3341                   if(buf[i-8] == '-' && buf[i-3] == 't')
3342                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3343                     collective = 1;
3344                     if(!strcmp("c-shouts", chatPartner[p])) {
3345                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3346                         chattingPartner = p; break;
3347                     }
3348                   }
3349                   if(chattingPartner < 0)
3350                   for(p=0; p<MAX_CHAT; p++) {
3351                     collective = 1;
3352                     if(!strcmp("shouts", chatPartner[p])) {
3353                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3354                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3355                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3356                         chattingPartner = p; break;
3357                     }
3358                   }
3359                 }
3360                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3361                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3362                     talker[0] = 0;
3363                     Colorize(ColorTell, FALSE);
3364                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3365                     collective |= 2;
3366                     chattingPartner = p; break;
3367                 }
3368                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3369                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3370                     started = STARTED_COMMENT;
3371                     parse_pos = 0; parse[0] = NULLCHAR;
3372                     savingComment = 3 + chattingPartner; // counts as TRUE
3373                     if(collective == 3) i = oldi; else {
3374                         suppressKibitz = TRUE;
3375                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3376                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3377                         continue;
3378                     }
3379                 }
3380             } // [HGM] chat: end of patch
3381
3382           backup = i;
3383             if (appData.zippyTalk || appData.zippyPlay) {
3384                 /* [DM] Backup address for color zippy lines */
3385 #if ZIPPY
3386                if (loggedOn == TRUE)
3387                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3388                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3389 #endif
3390             } // [DM] 'else { ' deleted
3391                 if (
3392                     /* Regular tells and says */
3393                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3394                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3395                     looking_at(buf, &i, "* says: ") ||
3396                     /* Don't color "message" or "messages" output */
3397                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3398                     looking_at(buf, &i, "*. * at *:*: ") ||
3399                     looking_at(buf, &i, "--* (*:*): ") ||
3400                     /* Message notifications (same color as tells) */
3401                     looking_at(buf, &i, "* has left a message ") ||
3402                     looking_at(buf, &i, "* just sent you a message:\n") ||
3403                     /* Whispers and kibitzes */
3404                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3405                     looking_at(buf, &i, "* kibitzes: ") ||
3406                     /* Channel tells */
3407                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3408
3409                   if (tkind == 1 && strchr(star_match[0], ':')) {
3410                       /* Avoid "tells you:" spoofs in channels */
3411                      tkind = 3;
3412                   }
3413                   if (star_match[0][0] == NULLCHAR ||
3414                       strchr(star_match[0], ' ') ||
3415                       (tkind == 3 && strchr(star_match[1], ' '))) {
3416                     /* Reject bogus matches */
3417                     i = oldi;
3418                   } else {
3419                     if (appData.colorize) {
3420                       if (oldi > next_out) {
3421                         SendToPlayer(&buf[next_out], oldi - next_out);
3422                         next_out = oldi;
3423                       }
3424                       switch (tkind) {
3425                       case 1:
3426                         Colorize(ColorTell, FALSE);
3427                         curColor = ColorTell;
3428                         break;
3429                       case 2:
3430                         Colorize(ColorKibitz, FALSE);
3431                         curColor = ColorKibitz;
3432                         break;
3433                       case 3:
3434                         p = strrchr(star_match[1], '(');
3435                         if (p == NULL) {
3436                           p = star_match[1];
3437                         } else {
3438                           p++;
3439                         }
3440                         if (atoi(p) == 1) {
3441                           Colorize(ColorChannel1, FALSE);
3442                           curColor = ColorChannel1;
3443                         } else {
3444                           Colorize(ColorChannel, FALSE);
3445                           curColor = ColorChannel;
3446                         }
3447                         break;
3448                       case 5:
3449                         curColor = ColorNormal;
3450                         break;
3451                       }
3452                     }
3453                     if (started == STARTED_NONE && appData.autoComment &&
3454                         (gameMode == IcsObserving ||
3455                          gameMode == IcsPlayingWhite ||
3456                          gameMode == IcsPlayingBlack)) {
3457                       parse_pos = i - oldi;
3458                       memcpy(parse, &buf[oldi], parse_pos);
3459                       parse[parse_pos] = NULLCHAR;
3460                       started = STARTED_COMMENT;
3461                       savingComment = TRUE;
3462                     } else if(collective != 3) {
3463                       started = STARTED_CHATTER;
3464                       savingComment = FALSE;
3465                     }
3466                     loggedOn = TRUE;
3467                     continue;
3468                   }
3469                 }
3470
3471                 if (looking_at(buf, &i, "* s-shouts: ") ||
3472                     looking_at(buf, &i, "* c-shouts: ")) {
3473                     if (appData.colorize) {
3474                         if (oldi > next_out) {
3475                             SendToPlayer(&buf[next_out], oldi - next_out);
3476                             next_out = oldi;
3477                         }
3478                         Colorize(ColorSShout, FALSE);
3479                         curColor = ColorSShout;
3480                     }
3481                     loggedOn = TRUE;
3482                     started = STARTED_CHATTER;
3483                     continue;
3484                 }
3485
3486                 if (looking_at(buf, &i, "--->")) {
3487                     loggedOn = TRUE;
3488                     continue;
3489                 }
3490
3491                 if (looking_at(buf, &i, "* shouts: ") ||
3492                     looking_at(buf, &i, "--> ")) {
3493                     if (appData.colorize) {
3494                         if (oldi > next_out) {
3495                             SendToPlayer(&buf[next_out], oldi - next_out);
3496                             next_out = oldi;
3497                         }
3498                         Colorize(ColorShout, FALSE);
3499                         curColor = ColorShout;
3500                     }
3501                     loggedOn = TRUE;
3502                     started = STARTED_CHATTER;
3503                     continue;
3504                 }
3505
3506                 if (looking_at( buf, &i, "Challenge:")) {
3507                     if (appData.colorize) {
3508                         if (oldi > next_out) {
3509                             SendToPlayer(&buf[next_out], oldi - next_out);
3510                             next_out = oldi;
3511                         }
3512                         Colorize(ColorChallenge, FALSE);
3513                         curColor = ColorChallenge;
3514                     }
3515                     loggedOn = TRUE;
3516                     continue;
3517                 }
3518
3519                 if (looking_at(buf, &i, "* offers you") ||
3520                     looking_at(buf, &i, "* offers to be") ||
3521                     looking_at(buf, &i, "* would like to") ||
3522                     looking_at(buf, &i, "* requests to") ||
3523                     looking_at(buf, &i, "Your opponent offers") ||
3524                     looking_at(buf, &i, "Your opponent requests")) {
3525
3526                     if (appData.colorize) {
3527                         if (oldi > next_out) {
3528                             SendToPlayer(&buf[next_out], oldi - next_out);
3529                             next_out = oldi;
3530                         }
3531                         Colorize(ColorRequest, FALSE);
3532                         curColor = ColorRequest;
3533                     }
3534                     continue;
3535                 }
3536
3537                 if (looking_at(buf, &i, "* (*) seeking")) {
3538                     if (appData.colorize) {
3539                         if (oldi > next_out) {
3540                             SendToPlayer(&buf[next_out], oldi - next_out);
3541                             next_out = oldi;
3542                         }
3543                         Colorize(ColorSeek, FALSE);
3544                         curColor = ColorSeek;
3545                     }
3546                     continue;
3547             }
3548
3549           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3550
3551             if (looking_at(buf, &i, "\\   ")) {
3552                 if (prevColor != ColorNormal) {
3553                     if (oldi > next_out) {
3554                         SendToPlayer(&buf[next_out], oldi - next_out);
3555                         next_out = oldi;
3556                     }
3557                     Colorize(prevColor, TRUE);
3558                     curColor = prevColor;
3559                 }
3560                 if (savingComment) {
3561                     parse_pos = i - oldi;
3562                     memcpy(parse, &buf[oldi], parse_pos);
3563                     parse[parse_pos] = NULLCHAR;
3564                     started = STARTED_COMMENT;
3565                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3566                         chattingPartner = savingComment - 3; // kludge to remember the box
3567                 } else {
3568                     started = STARTED_CHATTER;
3569                 }
3570                 continue;
3571             }
3572
3573             if (looking_at(buf, &i, "Black Strength :") ||
3574                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3575                 looking_at(buf, &i, "<10>") ||
3576                 looking_at(buf, &i, "#@#")) {
3577                 /* Wrong board style */
3578                 loggedOn = TRUE;
3579                 SendToICS(ics_prefix);
3580                 SendToICS("set style 12\n");
3581                 SendToICS(ics_prefix);
3582                 SendToICS("refresh\n");
3583                 continue;
3584             }
3585
3586             if (looking_at(buf, &i, "login:")) {
3587               if (!have_sent_ICS_logon) {
3588                 if(ICSInitScript())
3589                   have_sent_ICS_logon = 1;
3590                 else // no init script was found
3591                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3592               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3593                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3594               }
3595                 continue;
3596             }
3597
3598             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3599                 (looking_at(buf, &i, "\n<12> ") ||
3600                  looking_at(buf, &i, "<12> "))) {
3601                 loggedOn = TRUE;
3602                 if (oldi > next_out) {
3603                     SendToPlayer(&buf[next_out], oldi - next_out);
3604                 }
3605                 next_out = i;
3606                 started = STARTED_BOARD;
3607                 parse_pos = 0;
3608                 continue;
3609             }
3610
3611             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3612                 looking_at(buf, &i, "<b1> ")) {
3613                 if (oldi > next_out) {
3614                     SendToPlayer(&buf[next_out], oldi - next_out);
3615                 }
3616                 next_out = i;
3617                 started = STARTED_HOLDINGS;
3618                 parse_pos = 0;
3619                 continue;
3620             }
3621
3622             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3623                 loggedOn = TRUE;
3624                 /* Header for a move list -- first line */
3625
3626                 switch (ics_getting_history) {
3627                   case H_FALSE:
3628                     switch (gameMode) {
3629                       case IcsIdle:
3630                       case BeginningOfGame:
3631                         /* User typed "moves" or "oldmoves" while we
3632                            were idle.  Pretend we asked for these
3633                            moves and soak them up so user can step
3634                            through them and/or save them.
3635                            */
3636                         Reset(FALSE, TRUE);
3637                         gameMode = IcsObserving;
3638                         ModeHighlight();
3639                         ics_gamenum = -1;
3640                         ics_getting_history = H_GOT_UNREQ_HEADER;
3641                         break;
3642                       case EditGame: /*?*/
3643                       case EditPosition: /*?*/
3644                         /* Should above feature work in these modes too? */
3645                         /* For now it doesn't */
3646                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3647                         break;
3648                       default:
3649                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3650                         break;
3651                     }
3652                     break;
3653                   case H_REQUESTED:
3654                     /* Is this the right one? */
3655                     if (gameInfo.white && gameInfo.black &&
3656                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3657                         strcmp(gameInfo.black, star_match[2]) == 0) {
3658                         /* All is well */
3659                         ics_getting_history = H_GOT_REQ_HEADER;
3660                     }
3661                     break;
3662                   case H_GOT_REQ_HEADER:
3663                   case H_GOT_UNREQ_HEADER:
3664                   case H_GOT_UNWANTED_HEADER:
3665                   case H_GETTING_MOVES:
3666                     /* Should not happen */
3667                     DisplayError(_("Error gathering move list: two headers"), 0);
3668                     ics_getting_history = H_FALSE;
3669                     break;
3670                 }
3671
3672                 /* Save player ratings into gameInfo if needed */
3673                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3674                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3675                     (gameInfo.whiteRating == -1 ||
3676                      gameInfo.blackRating == -1)) {
3677
3678                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3679                     gameInfo.blackRating = string_to_rating(star_match[3]);
3680                     if (appData.debugMode)
3681                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3682                               gameInfo.whiteRating, gameInfo.blackRating);
3683                 }
3684                 continue;
3685             }
3686
3687             if (looking_at(buf, &i,
3688               "* * match, initial time: * minute*, increment: * second")) {
3689                 /* Header for a move list -- second line */
3690                 /* Initial board will follow if this is a wild game */
3691                 if (gameInfo.event != NULL) free(gameInfo.event);
3692                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3693                 gameInfo.event = StrSave(str);
3694                 /* [HGM] we switched variant. Translate boards if needed. */
3695                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3696                 continue;
3697             }
3698
3699             if (looking_at(buf, &i, "Move  ")) {
3700                 /* Beginning of a move list */
3701                 switch (ics_getting_history) {
3702                   case H_FALSE:
3703                     /* Normally should not happen */
3704                     /* Maybe user hit reset while we were parsing */
3705                     break;
3706                   case H_REQUESTED:
3707                     /* Happens if we are ignoring a move list that is not
3708                      * the one we just requested.  Common if the user
3709                      * tries to observe two games without turning off
3710                      * getMoveList */
3711                     break;
3712                   case H_GETTING_MOVES:
3713                     /* Should not happen */
3714                     DisplayError(_("Error gathering move list: nested"), 0);
3715                     ics_getting_history = H_FALSE;
3716                     break;
3717                   case H_GOT_REQ_HEADER:
3718                     ics_getting_history = H_GETTING_MOVES;
3719                     started = STARTED_MOVES;
3720                     parse_pos = 0;
3721                     if (oldi > next_out) {
3722                         SendToPlayer(&buf[next_out], oldi - next_out);
3723                     }
3724                     break;
3725                   case H_GOT_UNREQ_HEADER:
3726                     ics_getting_history = H_GETTING_MOVES;
3727                     started = STARTED_MOVES_NOHIDE;
3728                     parse_pos = 0;
3729                     break;
3730                   case H_GOT_UNWANTED_HEADER:
3731                     ics_getting_history = H_FALSE;
3732                     break;
3733                 }
3734                 continue;
3735             }
3736
3737             if (looking_at(buf, &i, "% ") ||
3738                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3739                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3740                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3741                     soughtPending = FALSE;
3742                     seekGraphUp = TRUE;
3743                     DrawSeekGraph();
3744                 }
3745                 if(suppressKibitz) next_out = i;
3746                 savingComment = FALSE;
3747                 suppressKibitz = 0;
3748                 switch (started) {
3749                   case STARTED_MOVES:
3750                   case STARTED_MOVES_NOHIDE:
3751                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3752                     parse[parse_pos + i - oldi] = NULLCHAR;
3753                     ParseGameHistory(parse);
3754 #if ZIPPY
3755                     if (appData.zippyPlay && first.initDone) {
3756                         FeedMovesToProgram(&first, forwardMostMove);
3757                         if (gameMode == IcsPlayingWhite) {
3758                             if (WhiteOnMove(forwardMostMove)) {
3759                                 if (first.sendTime) {
3760                                   if (first.useColors) {
3761                                     SendToProgram("black\n", &first);
3762                                   }
3763                                   SendTimeRemaining(&first, TRUE);
3764                                 }
3765                                 if (first.useColors) {
3766                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3767                                 }
3768                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3769                                 first.maybeThinking = TRUE;
3770                             } else {
3771                                 if (first.usePlayother) {
3772                                   if (first.sendTime) {
3773                                     SendTimeRemaining(&first, TRUE);
3774                                   }
3775                                   SendToProgram("playother\n", &first);
3776                                   firstMove = FALSE;
3777                                 } else {
3778                                   firstMove = TRUE;
3779                                 }
3780                             }
3781                         } else if (gameMode == IcsPlayingBlack) {
3782                             if (!WhiteOnMove(forwardMostMove)) {
3783                                 if (first.sendTime) {
3784                                   if (first.useColors) {
3785                                     SendToProgram("white\n", &first);
3786                                   }
3787                                   SendTimeRemaining(&first, FALSE);
3788                                 }
3789                                 if (first.useColors) {
3790                                   SendToProgram("black\n", &first);
3791                                 }
3792                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3793                                 first.maybeThinking = TRUE;
3794                             } else {
3795                                 if (first.usePlayother) {
3796                                   if (first.sendTime) {
3797                                     SendTimeRemaining(&first, FALSE);
3798                                   }
3799                                   SendToProgram("playother\n", &first);
3800                                   firstMove = FALSE;
3801                                 } else {
3802                                   firstMove = TRUE;
3803                                 }
3804                             }
3805                         }
3806                     }
3807 #endif
3808                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3809                         /* Moves came from oldmoves or moves command
3810                            while we weren't doing anything else.
3811                            */
3812                         currentMove = forwardMostMove;
3813                         ClearHighlights();/*!!could figure this out*/
3814                         flipView = appData.flipView;
3815                         DrawPosition(TRUE, boards[currentMove]);
3816                         DisplayBothClocks();
3817                         snprintf(str, MSG_SIZ, "%s %s %s",
3818                                 gameInfo.white, _("vs."),  gameInfo.black);
3819                         DisplayTitle(str);
3820                         gameMode = IcsIdle;
3821                     } else {
3822                         /* Moves were history of an active game */
3823                         if (gameInfo.resultDetails != NULL) {
3824                             free(gameInfo.resultDetails);
3825                             gameInfo.resultDetails = NULL;
3826                         }
3827                     }
3828                     HistorySet(parseList, backwardMostMove,
3829                                forwardMostMove, currentMove-1);
3830                     DisplayMove(currentMove - 1);
3831                     if (started == STARTED_MOVES) next_out = i;
3832                     started = STARTED_NONE;
3833                     ics_getting_history = H_FALSE;
3834                     break;
3835
3836                   case STARTED_OBSERVE:
3837                     started = STARTED_NONE;
3838                     SendToICS(ics_prefix);
3839                     SendToICS("refresh\n");
3840                     break;
3841
3842                   default:
3843                     break;
3844                 }
3845                 if(bookHit) { // [HGM] book: simulate book reply
3846                     static char bookMove[MSG_SIZ]; // a bit generous?
3847
3848                     programStats.nodes = programStats.depth = programStats.time =
3849                     programStats.score = programStats.got_only_move = 0;
3850                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3851
3852                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3853                     strcat(bookMove, bookHit);
3854                     HandleMachineMove(bookMove, &first);
3855                 }
3856                 continue;
3857             }
3858
3859             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3860                  started == STARTED_HOLDINGS ||
3861                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3862                 /* Accumulate characters in move list or board */
3863                 parse[parse_pos++] = buf[i];
3864             }
3865
3866             /* Start of game messages.  Mostly we detect start of game
3867                when the first board image arrives.  On some versions
3868                of the ICS, though, we need to do a "refresh" after starting
3869                to observe in order to get the current board right away. */
3870             if (looking_at(buf, &i, "Adding game * to observation list")) {
3871                 started = STARTED_OBSERVE;
3872                 continue;
3873             }
3874
3875             /* Handle auto-observe */
3876             if (appData.autoObserve &&
3877                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3878                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3879                 char *player;
3880                 /* Choose the player that was highlighted, if any. */
3881                 if (star_match[0][0] == '\033' ||
3882                     star_match[1][0] != '\033') {
3883                     player = star_match[0];
3884                 } else {
3885                     player = star_match[2];
3886                 }
3887                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3888                         ics_prefix, StripHighlightAndTitle(player));
3889                 SendToICS(str);
3890
3891                 /* Save ratings from notify string */
3892                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3893                 player1Rating = string_to_rating(star_match[1]);
3894                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3895                 player2Rating = string_to_rating(star_match[3]);
3896
3897                 if (appData.debugMode)
3898                   fprintf(debugFP,
3899                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3900                           player1Name, player1Rating,
3901                           player2Name, player2Rating);
3902
3903                 continue;
3904             }
3905
3906             /* Deal with automatic examine mode after a game,
3907                and with IcsObserving -> IcsExamining transition */
3908             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3909                 looking_at(buf, &i, "has made you an examiner of game *")) {
3910
3911                 int gamenum = atoi(star_match[0]);
3912                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3913                     gamenum == ics_gamenum) {
3914                     /* We were already playing or observing this game;
3915                        no need to refetch history */
3916                     gameMode = IcsExamining;
3917                     if (pausing) {
3918                         pauseExamForwardMostMove = forwardMostMove;
3919                     } else if (currentMove < forwardMostMove) {
3920                         ForwardInner(forwardMostMove);
3921                     }
3922                 } else {
3923                     /* I don't think this case really can happen */
3924                     SendToICS(ics_prefix);
3925                     SendToICS("refresh\n");
3926                 }
3927                 continue;
3928             }
3929
3930             /* Error messages */
3931 //          if (ics_user_moved) {
3932             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3933                 if (looking_at(buf, &i, "Illegal move") ||
3934                     looking_at(buf, &i, "Not a legal move") ||
3935                     looking_at(buf, &i, "Your king is in check") ||
3936                     looking_at(buf, &i, "It isn't your turn") ||
3937                     looking_at(buf, &i, "It is not your move")) {
3938                     /* Illegal move */
3939                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3940                         currentMove = forwardMostMove-1;
3941                         DisplayMove(currentMove - 1); /* before DMError */
3942                         DrawPosition(FALSE, boards[currentMove]);
3943                         SwitchClocks(forwardMostMove-1); // [HGM] race
3944                         DisplayBothClocks();
3945                     }
3946                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3947                     ics_user_moved = 0;
3948                     continue;
3949                 }
3950             }
3951
3952             if (looking_at(buf, &i, "still have time") ||
3953                 looking_at(buf, &i, "not out of time") ||
3954                 looking_at(buf, &i, "either player is out of time") ||
3955                 looking_at(buf, &i, "has timeseal; checking")) {
3956                 /* We must have called his flag a little too soon */
3957                 whiteFlag = blackFlag = FALSE;
3958                 continue;
3959             }
3960
3961             if (looking_at(buf, &i, "added * seconds to") ||
3962                 looking_at(buf, &i, "seconds were added to")) {
3963                 /* Update the clocks */
3964                 SendToICS(ics_prefix);
3965                 SendToICS("refresh\n");
3966                 continue;
3967             }
3968
3969             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3970                 ics_clock_paused = TRUE;
3971                 StopClocks();
3972                 continue;
3973             }
3974
3975             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3976                 ics_clock_paused = FALSE;
3977                 StartClocks();
3978                 continue;
3979             }
3980
3981             /* Grab player ratings from the Creating: message.
3982                Note we have to check for the special case when
3983                the ICS inserts things like [white] or [black]. */
3984             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3985                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3986                 /* star_matches:
3987                    0    player 1 name (not necessarily white)
3988                    1    player 1 rating
3989                    2    empty, white, or black (IGNORED)
3990                    3    player 2 name (not necessarily black)
3991                    4    player 2 rating
3992
3993                    The names/ratings are sorted out when the game
3994                    actually starts (below).
3995                 */
3996                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3997                 player1Rating = string_to_rating(star_match[1]);
3998                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3999                 player2Rating = string_to_rating(star_match[4]);
4000
4001                 if (appData.debugMode)
4002                   fprintf(debugFP,
4003                           "Ratings from 'Creating:' %s %d, %s %d\n",
4004                           player1Name, player1Rating,
4005                           player2Name, player2Rating);
4006
4007                 continue;
4008             }
4009
4010             /* Improved generic start/end-of-game messages */
4011             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4012                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4013                 /* If tkind == 0: */
4014                 /* star_match[0] is the game number */
4015                 /*           [1] is the white player's name */
4016                 /*           [2] is the black player's name */
4017                 /* For end-of-game: */
4018                 /*           [3] is the reason for the game end */
4019                 /*           [4] is a PGN end game-token, preceded by " " */
4020                 /* For start-of-game: */
4021                 /*           [3] begins with "Creating" or "Continuing" */
4022                 /*           [4] is " *" or empty (don't care). */
4023                 int gamenum = atoi(star_match[0]);
4024                 char *whitename, *blackname, *why, *endtoken;
4025                 ChessMove endtype = EndOfFile;
4026
4027                 if (tkind == 0) {
4028                   whitename = star_match[1];
4029                   blackname = star_match[2];
4030                   why = star_match[3];
4031                   endtoken = star_match[4];
4032                 } else {
4033                   whitename = star_match[1];
4034                   blackname = star_match[3];
4035                   why = star_match[5];
4036                   endtoken = star_match[6];
4037                 }
4038
4039                 /* Game start messages */
4040                 if (strncmp(why, "Creating ", 9) == 0 ||
4041                     strncmp(why, "Continuing ", 11) == 0) {
4042                     gs_gamenum = gamenum;
4043                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4044                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4045                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4046 #if ZIPPY
4047                     if (appData.zippyPlay) {
4048                         ZippyGameStart(whitename, blackname);
4049                     }
4050 #endif /*ZIPPY*/
4051                     partnerBoardValid = FALSE; // [HGM] bughouse
4052                     continue;
4053                 }
4054
4055                 /* Game end messages */
4056                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4057                     ics_gamenum != gamenum) {
4058                     continue;
4059                 }
4060                 while (endtoken[0] == ' ') endtoken++;
4061                 switch (endtoken[0]) {
4062                   case '*':
4063                   default:
4064                     endtype = GameUnfinished;
4065                     break;
4066                   case '0':
4067                     endtype = BlackWins;
4068                     break;
4069                   case '1':
4070                     if (endtoken[1] == '/')
4071                       endtype = GameIsDrawn;
4072                     else
4073                       endtype = WhiteWins;
4074                     break;
4075                 }
4076                 GameEnds(endtype, why, GE_ICS);
4077 #if ZIPPY
4078                 if (appData.zippyPlay && first.initDone) {
4079                     ZippyGameEnd(endtype, why);
4080                     if (first.pr == NoProc) {
4081                       /* Start the next process early so that we'll
4082                          be ready for the next challenge */
4083                       StartChessProgram(&first);
4084                     }
4085                     /* Send "new" early, in case this command takes
4086                        a long time to finish, so that we'll be ready
4087                        for the next challenge. */
4088                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4089                     Reset(TRUE, TRUE);
4090                 }
4091 #endif /*ZIPPY*/
4092                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4093                 continue;
4094             }
4095
4096             if (looking_at(buf, &i, "Removing game * from observation") ||
4097                 looking_at(buf, &i, "no longer observing game *") ||
4098                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4099                 if (gameMode == IcsObserving &&
4100                     atoi(star_match[0]) == ics_gamenum)
4101                   {
4102                       /* icsEngineAnalyze */
4103                       if (appData.icsEngineAnalyze) {
4104                             ExitAnalyzeMode();
4105                             ModeHighlight();
4106                       }
4107                       StopClocks();
4108                       gameMode = IcsIdle;
4109                       ics_gamenum = -1;
4110                       ics_user_moved = FALSE;
4111                   }
4112                 continue;
4113             }
4114
4115             if (looking_at(buf, &i, "no longer examining game *")) {
4116                 if (gameMode == IcsExamining &&
4117                     atoi(star_match[0]) == ics_gamenum)
4118                   {
4119                       gameMode = IcsIdle;
4120                       ics_gamenum = -1;
4121                       ics_user_moved = FALSE;
4122                   }
4123                 continue;
4124             }
4125
4126             /* Advance leftover_start past any newlines we find,
4127                so only partial lines can get reparsed */
4128             if (looking_at(buf, &i, "\n")) {
4129                 prevColor = curColor;
4130                 if (curColor != ColorNormal) {
4131                     if (oldi > next_out) {
4132                         SendToPlayer(&buf[next_out], oldi - next_out);
4133                         next_out = oldi;
4134                     }
4135                     Colorize(ColorNormal, FALSE);
4136                     curColor = ColorNormal;
4137                 }
4138                 if (started == STARTED_BOARD) {
4139                     started = STARTED_NONE;
4140                     parse[parse_pos] = NULLCHAR;
4141                     ParseBoard12(parse);
4142                     ics_user_moved = 0;
4143
4144                     /* Send premove here */
4145                     if (appData.premove) {
4146                       char str[MSG_SIZ];
4147                       if (currentMove == 0 &&
4148                           gameMode == IcsPlayingWhite &&
4149                           appData.premoveWhite) {
4150                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4151                         if (appData.debugMode)
4152                           fprintf(debugFP, "Sending premove:\n");
4153                         SendToICS(str);
4154                       } else if (currentMove == 1 &&
4155                                  gameMode == IcsPlayingBlack &&
4156                                  appData.premoveBlack) {
4157                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4158                         if (appData.debugMode)
4159                           fprintf(debugFP, "Sending premove:\n");
4160                         SendToICS(str);
4161                       } else if (gotPremove) {
4162                         gotPremove = 0;
4163                         ClearPremoveHighlights();
4164                         if (appData.debugMode)
4165                           fprintf(debugFP, "Sending premove:\n");
4166                           UserMoveEvent(premoveFromX, premoveFromY,
4167                                         premoveToX, premoveToY,
4168                                         premovePromoChar);
4169                       }
4170                     }
4171
4172                     /* Usually suppress following prompt */
4173                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4174                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4175                         if (looking_at(buf, &i, "*% ")) {
4176                             savingComment = FALSE;
4177                             suppressKibitz = 0;
4178                         }
4179                     }
4180                     next_out = i;
4181                 } else if (started == STARTED_HOLDINGS) {
4182                     int gamenum;
4183                     char new_piece[MSG_SIZ];
4184                     started = STARTED_NONE;
4185                     parse[parse_pos] = NULLCHAR;
4186                     if (appData.debugMode)
4187                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4188                                                         parse, currentMove);
4189                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4190                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4191                         if (gameInfo.variant == VariantNormal) {
4192                           /* [HGM] We seem to switch variant during a game!
4193                            * Presumably no holdings were displayed, so we have
4194                            * to move the position two files to the right to
4195                            * create room for them!
4196                            */
4197                           VariantClass newVariant;
4198                           switch(gameInfo.boardWidth) { // base guess on board width
4199                                 case 9:  newVariant = VariantShogi; break;
4200                                 case 10: newVariant = VariantGreat; break;
4201                                 default: newVariant = VariantCrazyhouse; break;
4202                           }
4203                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4204                           /* Get a move list just to see the header, which
4205                              will tell us whether this is really bug or zh */
4206                           if (ics_getting_history == H_FALSE) {
4207                             ics_getting_history = H_REQUESTED;
4208                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4209                             SendToICS(str);
4210                           }
4211                         }
4212                         new_piece[0] = NULLCHAR;
4213                         sscanf(parse, "game %d white [%s black [%s <- %s",
4214                                &gamenum, white_holding, black_holding,
4215                                new_piece);
4216                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4217                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4218                         /* [HGM] copy holdings to board holdings area */
4219                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4220                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4221                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4222 #if ZIPPY
4223                         if (appData.zippyPlay && first.initDone) {
4224                             ZippyHoldings(white_holding, black_holding,
4225                                           new_piece);
4226                         }
4227 #endif /*ZIPPY*/
4228                         if (tinyLayout || smallLayout) {
4229                             char wh[16], bh[16];
4230                             PackHolding(wh, white_holding);
4231                             PackHolding(bh, black_holding);
4232                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4233                                     gameInfo.white, gameInfo.black);
4234                         } else {
4235                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4236                                     gameInfo.white, white_holding, _("vs."),
4237                                     gameInfo.black, black_holding);
4238                         }
4239                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4240                         DrawPosition(FALSE, boards[currentMove]);
4241                         DisplayTitle(str);
4242                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4243                         sscanf(parse, "game %d white [%s black [%s <- %s",
4244                                &gamenum, white_holding, black_holding,
4245                                new_piece);
4246                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4247                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4248                         /* [HGM] copy holdings to partner-board holdings area */
4249                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4250                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4251                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4252                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4253                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4254                       }
4255                     }
4256                     /* Suppress following prompt */
4257                     if (looking_at(buf, &i, "*% ")) {
4258                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4259                         savingComment = FALSE;
4260                         suppressKibitz = 0;
4261                     }
4262                     next_out = i;
4263                 }
4264                 continue;
4265             }
4266
4267             i++;                /* skip unparsed character and loop back */
4268         }
4269
4270         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4271 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4272 //          SendToPlayer(&buf[next_out], i - next_out);
4273             started != STARTED_HOLDINGS && leftover_start > next_out) {
4274             SendToPlayer(&buf[next_out], leftover_start - next_out);
4275             next_out = i;
4276         }
4277
4278         leftover_len = buf_len - leftover_start;
4279         /* if buffer ends with something we couldn't parse,
4280            reparse it after appending the next read */
4281
4282     } else if (count == 0) {
4283         RemoveInputSource(isr);
4284         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4285     } else {
4286         DisplayFatalError(_("Error reading from ICS"), error, 1);
4287     }
4288 }
4289
4290
4291 /* Board style 12 looks like this:
4292
4293    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4294
4295  * The "<12> " is stripped before it gets to this routine.  The two
4296  * trailing 0's (flip state and clock ticking) are later addition, and
4297  * some chess servers may not have them, or may have only the first.
4298  * Additional trailing fields may be added in the future.
4299  */
4300
4301 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4302
4303 #define RELATION_OBSERVING_PLAYED    0
4304 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4305 #define RELATION_PLAYING_MYMOVE      1
4306 #define RELATION_PLAYING_NOTMYMOVE  -1
4307 #define RELATION_EXAMINING           2
4308 #define RELATION_ISOLATED_BOARD     -3
4309 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4310
4311 void
4312 ParseBoard12 (char *string)
4313 {
4314 #if ZIPPY
4315     int i, takeback;
4316     char *bookHit = NULL; // [HGM] book
4317 #endif
4318     GameMode newGameMode;
4319     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4320     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4321     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4322     char to_play, board_chars[200];
4323     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4324     char black[32], white[32];
4325     Board board;
4326     int prevMove = currentMove;
4327     int ticking = 2;
4328     ChessMove moveType;
4329     int fromX, fromY, toX, toY;
4330     char promoChar;
4331     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4332     Boolean weird = FALSE, reqFlag = FALSE;
4333
4334     fromX = fromY = toX = toY = -1;
4335
4336     newGame = FALSE;
4337
4338     if (appData.debugMode)
4339       fprintf(debugFP, "Parsing board: %s\n", string);
4340
4341     move_str[0] = NULLCHAR;
4342     elapsed_time[0] = NULLCHAR;
4343     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4344         int  i = 0, j;
4345         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4346             if(string[i] == ' ') { ranks++; files = 0; }
4347             else files++;
4348             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4349             i++;
4350         }
4351         for(j = 0; j <i; j++) board_chars[j] = string[j];
4352         board_chars[i] = '\0';
4353         string += i + 1;
4354     }
4355     n = sscanf(string, PATTERN, &to_play, &double_push,
4356                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4357                &gamenum, white, black, &relation, &basetime, &increment,
4358                &white_stren, &black_stren, &white_time, &black_time,
4359                &moveNum, str, elapsed_time, move_str, &ics_flip,
4360                &ticking);
4361
4362     if (n < 21) {
4363         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4364         DisplayError(str, 0);
4365         return;
4366     }
4367
4368     /* Convert the move number to internal form */
4369     moveNum = (moveNum - 1) * 2;
4370     if (to_play == 'B') moveNum++;
4371     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4372       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4373                         0, 1);
4374       return;
4375     }
4376
4377     switch (relation) {
4378       case RELATION_OBSERVING_PLAYED:
4379       case RELATION_OBSERVING_STATIC:
4380         if (gamenum == -1) {
4381             /* Old ICC buglet */
4382             relation = RELATION_OBSERVING_STATIC;
4383         }
4384         newGameMode = IcsObserving;
4385         break;
4386       case RELATION_PLAYING_MYMOVE:
4387       case RELATION_PLAYING_NOTMYMOVE:
4388         newGameMode =
4389           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4390             IcsPlayingWhite : IcsPlayingBlack;
4391         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4392         break;
4393       case RELATION_EXAMINING:
4394         newGameMode = IcsExamining;
4395         break;
4396       case RELATION_ISOLATED_BOARD:
4397       default:
4398         /* Just display this board.  If user was doing something else,
4399            we will forget about it until the next board comes. */
4400         newGameMode = IcsIdle;
4401         break;
4402       case RELATION_STARTING_POSITION:
4403         newGameMode = gameMode;
4404         break;
4405     }
4406
4407     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4408         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4409          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4410       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4411       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4412       static int lastBgGame = -1;
4413       char *toSqr;
4414       for (k = 0; k < ranks; k++) {
4415         for (j = 0; j < files; j++)
4416           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4417         if(gameInfo.holdingsWidth > 1) {
4418              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4419              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4420         }
4421       }
4422       CopyBoard(partnerBoard, board);
4423       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4424         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4425         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4426       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4427       if(toSqr = strchr(str, '-')) {
4428         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4429         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4430       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4431       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4432       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4433       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4434       if(twoBoards) {
4435           DisplayWhiteClock(white_time*fac, to_play == 'W');
4436           DisplayBlackClock(black_time*fac, to_play != 'W');
4437           activePartner = to_play;
4438           if(gamenum != lastBgGame) {
4439               char buf[MSG_SIZ];
4440               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4441               DisplayTitle(buf);
4442           }
4443           lastBgGame = gamenum;
4444           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4445                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4446       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4447                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4448       if(!twoBoards) DisplayMessage(partnerStatus, "");
4449         partnerBoardValid = TRUE;
4450       return;
4451     }
4452
4453     if(appData.dualBoard && appData.bgObserve) {
4454         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4455             SendToICS(ics_prefix), SendToICS("pobserve\n");
4456         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4457             char buf[MSG_SIZ];
4458             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4459             SendToICS(buf);
4460         }
4461     }
4462
4463     /* Modify behavior for initial board display on move listing
4464        of wild games.
4465        */
4466     switch (ics_getting_history) {
4467       case H_FALSE:
4468       case H_REQUESTED:
4469         break;
4470       case H_GOT_REQ_HEADER:
4471       case H_GOT_UNREQ_HEADER:
4472         /* This is the initial position of the current game */
4473         gamenum = ics_gamenum;
4474         moveNum = 0;            /* old ICS bug workaround */
4475         if (to_play == 'B') {
4476           startedFromSetupPosition = TRUE;
4477           blackPlaysFirst = TRUE;
4478           moveNum = 1;
4479           if (forwardMostMove == 0) forwardMostMove = 1;
4480           if (backwardMostMove == 0) backwardMostMove = 1;
4481           if (currentMove == 0) currentMove = 1;
4482         }
4483         newGameMode = gameMode;
4484         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4485         break;
4486       case H_GOT_UNWANTED_HEADER:
4487         /* This is an initial board that we don't want */
4488         return;
4489       case H_GETTING_MOVES:
4490         /* Should not happen */
4491         DisplayError(_("Error gathering move list: extra board"), 0);
4492         ics_getting_history = H_FALSE;
4493         return;
4494     }
4495
4496    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4497                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4498                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4499      /* [HGM] We seem to have switched variant unexpectedly
4500       * Try to guess new variant from board size
4501       */
4502           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4503           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4504           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4505           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4506           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4507           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4508           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4509           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4510           /* Get a move list just to see the header, which
4511              will tell us whether this is really bug or zh */
4512           if (ics_getting_history == H_FALSE) {
4513             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4514             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4515             SendToICS(str);
4516           }
4517     }
4518
4519     /* Take action if this is the first board of a new game, or of a
4520        different game than is currently being displayed.  */
4521     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4522         relation == RELATION_ISOLATED_BOARD) {
4523
4524         /* Forget the old game and get the history (if any) of the new one */
4525         if (gameMode != BeginningOfGame) {
4526           Reset(TRUE, TRUE);
4527         }
4528         newGame = TRUE;
4529         if (appData.autoRaiseBoard) BoardToTop();
4530         prevMove = -3;
4531         if (gamenum == -1) {
4532             newGameMode = IcsIdle;
4533         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4534                    appData.getMoveList && !reqFlag) {
4535             /* Need to get game history */
4536             ics_getting_history = H_REQUESTED;
4537             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4538             SendToICS(str);
4539         }
4540
4541         /* Initially flip the board to have black on the bottom if playing
4542            black or if the ICS flip flag is set, but let the user change
4543            it with the Flip View button. */
4544         flipView = appData.autoFlipView ?
4545           (newGameMode == IcsPlayingBlack) || ics_flip :
4546           appData.flipView;
4547
4548         /* Done with values from previous mode; copy in new ones */
4549         gameMode = newGameMode;
4550         ModeHighlight();
4551         ics_gamenum = gamenum;
4552         if (gamenum == gs_gamenum) {
4553             int klen = strlen(gs_kind);
4554             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4555             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4556             gameInfo.event = StrSave(str);
4557         } else {
4558             gameInfo.event = StrSave("ICS game");
4559         }
4560         gameInfo.site = StrSave(appData.icsHost);
4561         gameInfo.date = PGNDate();
4562         gameInfo.round = StrSave("-");
4563         gameInfo.white = StrSave(white);
4564         gameInfo.black = StrSave(black);
4565         timeControl = basetime * 60 * 1000;
4566         timeControl_2 = 0;
4567         timeIncrement = increment * 1000;
4568         movesPerSession = 0;
4569         gameInfo.timeControl = TimeControlTagValue();
4570         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4571   if (appData.debugMode) {
4572     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4573     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4574     setbuf(debugFP, NULL);
4575   }
4576
4577         gameInfo.outOfBook = NULL;
4578
4579         /* Do we have the ratings? */
4580         if (strcmp(player1Name, white) == 0 &&
4581             strcmp(player2Name, black) == 0) {
4582             if (appData.debugMode)
4583               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4584                       player1Rating, player2Rating);
4585             gameInfo.whiteRating = player1Rating;
4586             gameInfo.blackRating = player2Rating;
4587         } else if (strcmp(player2Name, white) == 0 &&
4588                    strcmp(player1Name, black) == 0) {
4589             if (appData.debugMode)
4590               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4591                       player2Rating, player1Rating);
4592             gameInfo.whiteRating = player2Rating;
4593             gameInfo.blackRating = player1Rating;
4594         }
4595         player1Name[0] = player2Name[0] = NULLCHAR;
4596
4597         /* Silence shouts if requested */
4598         if (appData.quietPlay &&
4599             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4600             SendToICS(ics_prefix);
4601             SendToICS("set shout 0\n");
4602         }
4603     }
4604
4605     /* Deal with midgame name changes */
4606     if (!newGame) {
4607         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4608             if (gameInfo.white) free(gameInfo.white);
4609             gameInfo.white = StrSave(white);
4610         }
4611         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4612             if (gameInfo.black) free(gameInfo.black);
4613             gameInfo.black = StrSave(black);
4614         }
4615     }
4616
4617     /* Throw away game result if anything actually changes in examine mode */
4618     if (gameMode == IcsExamining && !newGame) {
4619         gameInfo.result = GameUnfinished;
4620         if (gameInfo.resultDetails != NULL) {
4621             free(gameInfo.resultDetails);
4622             gameInfo.resultDetails = NULL;
4623         }
4624     }
4625
4626     /* In pausing && IcsExamining mode, we ignore boards coming
4627        in if they are in a different variation than we are. */
4628     if (pauseExamInvalid) return;
4629     if (pausing && gameMode == IcsExamining) {
4630         if (moveNum <= pauseExamForwardMostMove) {
4631             pauseExamInvalid = TRUE;
4632             forwardMostMove = pauseExamForwardMostMove;
4633             return;
4634         }
4635     }
4636
4637   if (appData.debugMode) {
4638     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4639   }
4640     /* Parse the board */
4641     for (k = 0; k < ranks; k++) {
4642       for (j = 0; j < files; j++)
4643         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4644       if(gameInfo.holdingsWidth > 1) {
4645            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4646            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4647       }
4648     }
4649     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4650       board[5][BOARD_RGHT+1] = WhiteAngel;
4651       board[6][BOARD_RGHT+1] = WhiteMarshall;
4652       board[1][0] = BlackMarshall;
4653       board[2][0] = BlackAngel;
4654       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4655     }
4656     CopyBoard(boards[moveNum], board);
4657     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4658     if (moveNum == 0) {
4659         startedFromSetupPosition =
4660           !CompareBoards(board, initialPosition);
4661         if(startedFromSetupPosition)
4662             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4663     }
4664
4665     /* [HGM] Set castling rights. Take the outermost Rooks,
4666        to make it also work for FRC opening positions. Note that board12
4667        is really defective for later FRC positions, as it has no way to
4668        indicate which Rook can castle if they are on the same side of King.
4669        For the initial position we grant rights to the outermost Rooks,
4670        and remember thos rights, and we then copy them on positions
4671        later in an FRC game. This means WB might not recognize castlings with
4672        Rooks that have moved back to their original position as illegal,
4673        but in ICS mode that is not its job anyway.
4674     */
4675     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4676     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4677
4678         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4679             if(board[0][i] == WhiteRook) j = i;
4680         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4681         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4682             if(board[0][i] == WhiteRook) j = i;
4683         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4684         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4685             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4686         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4687         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4688             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4689         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4690
4691         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4692         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4693         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4694             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4695         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4696             if(board[BOARD_HEIGHT-1][k] == bKing)
4697                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4698         if(gameInfo.variant == VariantTwoKings) {
4699             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4700             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4701             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4702         }
4703     } else { int r;
4704         r = boards[moveNum][CASTLING][0] = initialRights[0];
4705         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4706         r = boards[moveNum][CASTLING][1] = initialRights[1];
4707         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4708         r = boards[moveNum][CASTLING][3] = initialRights[3];
4709         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4710         r = boards[moveNum][CASTLING][4] = initialRights[4];
4711         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4712         /* wildcastle kludge: always assume King has rights */
4713         r = boards[moveNum][CASTLING][2] = initialRights[2];
4714         r = boards[moveNum][CASTLING][5] = initialRights[5];
4715     }
4716     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4717     boards[moveNum][EP_STATUS] = EP_NONE;
4718     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4719     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4720     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4721
4722
4723     if (ics_getting_history == H_GOT_REQ_HEADER ||
4724         ics_getting_history == H_GOT_UNREQ_HEADER) {
4725         /* This was an initial position from a move list, not
4726            the current position */
4727         return;
4728     }
4729
4730     /* Update currentMove and known move number limits */
4731     newMove = newGame || moveNum > forwardMostMove;
4732
4733     if (newGame) {
4734         forwardMostMove = backwardMostMove = currentMove = moveNum;
4735         if (gameMode == IcsExamining && moveNum == 0) {
4736           /* Workaround for ICS limitation: we are not told the wild
4737              type when starting to examine a game.  But if we ask for
4738              the move list, the move list header will tell us */
4739             ics_getting_history = H_REQUESTED;
4740             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4741             SendToICS(str);
4742         }
4743     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4744                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4745 #if ZIPPY
4746         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4747         /* [HGM] applied this also to an engine that is silently watching        */
4748         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4749             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4750             gameInfo.variant == currentlyInitializedVariant) {
4751           takeback = forwardMostMove - moveNum;
4752           for (i = 0; i < takeback; i++) {
4753             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4754             SendToProgram("undo\n", &first);
4755           }
4756         }
4757 #endif
4758
4759         forwardMostMove = moveNum;
4760         if (!pausing || currentMove > forwardMostMove)
4761           currentMove = forwardMostMove;
4762     } else {
4763         /* New part of history that is not contiguous with old part */
4764         if (pausing && gameMode == IcsExamining) {
4765             pauseExamInvalid = TRUE;
4766             forwardMostMove = pauseExamForwardMostMove;
4767             return;
4768         }
4769         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4770 #if ZIPPY
4771             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4772                 // [HGM] when we will receive the move list we now request, it will be
4773                 // fed to the engine from the first move on. So if the engine is not
4774                 // in the initial position now, bring it there.
4775                 InitChessProgram(&first, 0);
4776             }
4777 #endif
4778             ics_getting_history = H_REQUESTED;
4779             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4780             SendToICS(str);
4781         }
4782         forwardMostMove = backwardMostMove = currentMove = moveNum;
4783     }
4784
4785     /* Update the clocks */
4786     if (strchr(elapsed_time, '.')) {
4787       /* Time is in ms */
4788       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4789       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4790     } else {
4791       /* Time is in seconds */
4792       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4793       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4794     }
4795
4796
4797 #if ZIPPY
4798     if (appData.zippyPlay && newGame &&
4799         gameMode != IcsObserving && gameMode != IcsIdle &&
4800         gameMode != IcsExamining)
4801       ZippyFirstBoard(moveNum, basetime, increment);
4802 #endif
4803
4804     /* Put the move on the move list, first converting
4805        to canonical algebraic form. */
4806     if (moveNum > 0) {
4807   if (appData.debugMode) {
4808     int f = forwardMostMove;
4809     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4810             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4811             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4812     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4813     fprintf(debugFP, "moveNum = %d\n", moveNum);
4814     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4815     setbuf(debugFP, NULL);
4816   }
4817         if (moveNum <= backwardMostMove) {
4818             /* We don't know what the board looked like before
4819                this move.  Punt. */
4820           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4821             strcat(parseList[moveNum - 1], " ");
4822             strcat(parseList[moveNum - 1], elapsed_time);
4823             moveList[moveNum - 1][0] = NULLCHAR;
4824         } else if (strcmp(move_str, "none") == 0) {
4825             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4826             /* Again, we don't know what the board looked like;
4827                this is really the start of the game. */
4828             parseList[moveNum - 1][0] = NULLCHAR;
4829             moveList[moveNum - 1][0] = NULLCHAR;
4830             backwardMostMove = moveNum;
4831             startedFromSetupPosition = TRUE;
4832             fromX = fromY = toX = toY = -1;
4833         } else {
4834           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4835           //                 So we parse the long-algebraic move string in stead of the SAN move
4836           int valid; char buf[MSG_SIZ], *prom;
4837
4838           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4839                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4840           // str looks something like "Q/a1-a2"; kill the slash
4841           if(str[1] == '/')
4842             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4843           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4844           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4845                 strcat(buf, prom); // long move lacks promo specification!
4846           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4847                 if(appData.debugMode)
4848                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4849                 safeStrCpy(move_str, buf, MSG_SIZ);
4850           }
4851           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4852                                 &fromX, &fromY, &toX, &toY, &promoChar)
4853                || ParseOneMove(buf, moveNum - 1, &moveType,
4854                                 &fromX, &fromY, &toX, &toY, &promoChar);
4855           // end of long SAN patch
4856           if (valid) {
4857             (void) CoordsToAlgebraic(boards[moveNum - 1],
4858                                      PosFlags(moveNum - 1),
4859                                      fromY, fromX, toY, toX, promoChar,
4860                                      parseList[moveNum-1]);
4861             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4862               case MT_NONE:
4863               case MT_STALEMATE:
4864               default:
4865                 break;
4866               case MT_CHECK:
4867                 if(!IS_SHOGI(gameInfo.variant))
4868                     strcat(parseList[moveNum - 1], "+");
4869                 break;
4870               case MT_CHECKMATE:
4871               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4872                 strcat(parseList[moveNum - 1], "#");
4873                 break;
4874             }
4875             strcat(parseList[moveNum - 1], " ");
4876             strcat(parseList[moveNum - 1], elapsed_time);
4877             /* currentMoveString is set as a side-effect of ParseOneMove */
4878             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4879             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4880             strcat(moveList[moveNum - 1], "\n");
4881
4882             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4883                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4884               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4885                 ChessSquare old, new = boards[moveNum][k][j];
4886                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4887                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4888                   if(old == new) continue;
4889                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4890                   else if(new == WhiteWazir || new == BlackWazir) {
4891                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4892                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4893                       else boards[moveNum][k][j] = old; // preserve type of Gold
4894                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4895                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4896               }
4897           } else {
4898             /* Move from ICS was illegal!?  Punt. */
4899             if (appData.debugMode) {
4900               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4901               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4902             }
4903             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4904             strcat(parseList[moveNum - 1], " ");
4905             strcat(parseList[moveNum - 1], elapsed_time);
4906             moveList[moveNum - 1][0] = NULLCHAR;
4907             fromX = fromY = toX = toY = -1;
4908           }
4909         }
4910   if (appData.debugMode) {
4911     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4912     setbuf(debugFP, NULL);
4913   }
4914
4915 #if ZIPPY
4916         /* Send move to chess program (BEFORE animating it). */
4917         if (appData.zippyPlay && !newGame && newMove &&
4918            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4919
4920             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4921                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4922                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4923                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4924                             move_str);
4925                     DisplayError(str, 0);
4926                 } else {
4927                     if (first.sendTime) {
4928                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4929                     }
4930                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4931                     if (firstMove && !bookHit) {
4932                         firstMove = FALSE;
4933                         if (first.useColors) {
4934                           SendToProgram(gameMode == IcsPlayingWhite ?
4935                                         "white\ngo\n" :
4936                                         "black\ngo\n", &first);
4937                         } else {
4938                           SendToProgram("go\n", &first);
4939                         }
4940                         first.maybeThinking = TRUE;
4941                     }
4942                 }
4943             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4944               if (moveList[moveNum - 1][0] == NULLCHAR) {
4945                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4946                 DisplayError(str, 0);
4947               } else {
4948                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4949                 SendMoveToProgram(moveNum - 1, &first);
4950               }
4951             }
4952         }
4953 #endif
4954     }
4955
4956     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4957         /* If move comes from a remote source, animate it.  If it
4958            isn't remote, it will have already been animated. */
4959         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4960             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4961         }
4962         if (!pausing && appData.highlightLastMove) {
4963             SetHighlights(fromX, fromY, toX, toY);
4964         }
4965     }
4966
4967     /* Start the clocks */
4968     whiteFlag = blackFlag = FALSE;
4969     appData.clockMode = !(basetime == 0 && increment == 0);
4970     if (ticking == 0) {
4971       ics_clock_paused = TRUE;
4972       StopClocks();
4973     } else if (ticking == 1) {
4974       ics_clock_paused = FALSE;
4975     }
4976     if (gameMode == IcsIdle ||
4977         relation == RELATION_OBSERVING_STATIC ||
4978         relation == RELATION_EXAMINING ||
4979         ics_clock_paused)
4980       DisplayBothClocks();
4981     else
4982       StartClocks();
4983
4984     /* Display opponents and material strengths */
4985     if (gameInfo.variant != VariantBughouse &&
4986         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4987         if (tinyLayout || smallLayout) {
4988             if(gameInfo.variant == VariantNormal)
4989               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4990                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4991                     basetime, increment);
4992             else
4993               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4994                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4995                     basetime, increment, (int) gameInfo.variant);
4996         } else {
4997             if(gameInfo.variant == VariantNormal)
4998               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4999                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5000                     basetime, increment);
5001             else
5002               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5003                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5004                     basetime, increment, VariantName(gameInfo.variant));
5005         }
5006         DisplayTitle(str);
5007   if (appData.debugMode) {
5008     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5009   }
5010     }
5011
5012
5013     /* Display the board */
5014     if (!pausing && !appData.noGUI) {
5015
5016       if (appData.premove)
5017           if (!gotPremove ||
5018              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5019              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5020               ClearPremoveHighlights();
5021
5022       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5023         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5024       DrawPosition(j, boards[currentMove]);
5025
5026       DisplayMove(moveNum - 1);
5027       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5028             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5029               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5030         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5031       }
5032     }
5033
5034     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5035 #if ZIPPY
5036     if(bookHit) { // [HGM] book: simulate book reply
5037         static char bookMove[MSG_SIZ]; // a bit generous?
5038
5039         programStats.nodes = programStats.depth = programStats.time =
5040         programStats.score = programStats.got_only_move = 0;
5041         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5042
5043         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5044         strcat(bookMove, bookHit);
5045         HandleMachineMove(bookMove, &first);
5046     }
5047 #endif
5048 }
5049
5050 void
5051 GetMoveListEvent ()
5052 {
5053     char buf[MSG_SIZ];
5054     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5055         ics_getting_history = H_REQUESTED;
5056         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5057         SendToICS(buf);
5058     }
5059 }
5060
5061 void
5062 SendToBoth (char *msg)
5063 {   // to make it easy to keep two engines in step in dual analysis
5064     SendToProgram(msg, &first);
5065     if(second.analyzing) SendToProgram(msg, &second);
5066 }
5067
5068 void
5069 AnalysisPeriodicEvent (int force)
5070 {
5071     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5072          && !force) || !appData.periodicUpdates)
5073       return;
5074
5075     /* Send . command to Crafty to collect stats */
5076     SendToBoth(".\n");
5077
5078     /* Don't send another until we get a response (this makes
5079        us stop sending to old Crafty's which don't understand
5080        the "." command (sending illegal cmds resets node count & time,
5081        which looks bad)) */
5082     programStats.ok_to_send = 0;
5083 }
5084
5085 void
5086 ics_update_width (int new_width)
5087 {
5088         ics_printf("set width %d\n", new_width);
5089 }
5090
5091 void
5092 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5093 {
5094     char buf[MSG_SIZ];
5095
5096     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5097         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5098             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5099             SendToProgram(buf, cps);
5100             return;
5101         }
5102         // null move in variant where engine does not understand it (for analysis purposes)
5103         SendBoard(cps, moveNum + 1); // send position after move in stead.
5104         return;
5105     }
5106     if (cps->useUsermove) {
5107       SendToProgram("usermove ", cps);
5108     }
5109     if (cps->useSAN) {
5110       char *space;
5111       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5112         int len = space - parseList[moveNum];
5113         memcpy(buf, parseList[moveNum], len);
5114         buf[len++] = '\n';
5115         buf[len] = NULLCHAR;
5116       } else {
5117         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5118       }
5119       SendToProgram(buf, cps);
5120     } else {
5121       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5122         AlphaRank(moveList[moveNum], 4);
5123         SendToProgram(moveList[moveNum], cps);
5124         AlphaRank(moveList[moveNum], 4); // and back
5125       } else
5126       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5127        * the engine. It would be nice to have a better way to identify castle
5128        * moves here. */
5129       if(appData.fischerCastling && cps->useOOCastle) {
5130         int fromX = moveList[moveNum][0] - AAA;
5131         int fromY = moveList[moveNum][1] - ONE;
5132         int toX = moveList[moveNum][2] - AAA;
5133         int toY = moveList[moveNum][3] - ONE;
5134         if((boards[moveNum][fromY][fromX] == WhiteKing
5135             && boards[moveNum][toY][toX] == WhiteRook)
5136            || (boards[moveNum][fromY][fromX] == BlackKing
5137                && boards[moveNum][toY][toX] == BlackRook)) {
5138           if(toX > fromX) SendToProgram("O-O\n", cps);
5139           else SendToProgram("O-O-O\n", cps);
5140         }
5141         else SendToProgram(moveList[moveNum], cps);
5142       } else
5143       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5144           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
5145                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5146                                                moveList[moveNum][5], moveList[moveNum][6] - '0',
5147                                                moveList[moveNum][2], moveList[moveNum][3] - '0');
5148           SendToProgram(buf, cps);
5149       } else
5150       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5151         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5152           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5153           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5154                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5155         } else
5156           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5157                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5158         SendToProgram(buf, cps);
5159       }
5160       else SendToProgram(moveList[moveNum], cps);
5161       /* End of additions by Tord */
5162     }
5163
5164     /* [HGM] setting up the opening has brought engine in force mode! */
5165     /*       Send 'go' if we are in a mode where machine should play. */
5166     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5167         (gameMode == TwoMachinesPlay   ||
5168 #if ZIPPY
5169          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5170 #endif
5171          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5172         SendToProgram("go\n", cps);
5173   if (appData.debugMode) {
5174     fprintf(debugFP, "(extra)\n");
5175   }
5176     }
5177     setboardSpoiledMachineBlack = 0;
5178 }
5179
5180 void
5181 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5182 {
5183     char user_move[MSG_SIZ];
5184     char suffix[4];
5185
5186     if(gameInfo.variant == VariantSChess && promoChar) {
5187         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5188         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5189     } else suffix[0] = NULLCHAR;
5190
5191     switch (moveType) {
5192       default:
5193         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5194                 (int)moveType, fromX, fromY, toX, toY);
5195         DisplayError(user_move + strlen("say "), 0);
5196         break;
5197       case WhiteKingSideCastle:
5198       case BlackKingSideCastle:
5199       case WhiteQueenSideCastleWild:
5200       case BlackQueenSideCastleWild:
5201       /* PUSH Fabien */
5202       case WhiteHSideCastleFR:
5203       case BlackHSideCastleFR:
5204       /* POP Fabien */
5205         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5206         break;
5207       case WhiteQueenSideCastle:
5208       case BlackQueenSideCastle:
5209       case WhiteKingSideCastleWild:
5210       case BlackKingSideCastleWild:
5211       /* PUSH Fabien */
5212       case WhiteASideCastleFR:
5213       case BlackASideCastleFR:
5214       /* POP Fabien */
5215         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5216         break;
5217       case WhiteNonPromotion:
5218       case BlackNonPromotion:
5219         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5220         break;
5221       case WhitePromotion:
5222       case BlackPromotion:
5223         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5224            gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
5225           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5226                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5227                 PieceToChar(WhiteFerz));
5228         else if(gameInfo.variant == VariantGreat)
5229           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5230                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5231                 PieceToChar(WhiteMan));
5232         else
5233           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5234                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5235                 promoChar);
5236         break;
5237       case WhiteDrop:
5238       case BlackDrop:
5239       drop:
5240         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5241                  ToUpper(PieceToChar((ChessSquare) fromX)),
5242                  AAA + toX, ONE + toY);
5243         break;
5244       case IllegalMove:  /* could be a variant we don't quite understand */
5245         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5246       case NormalMove:
5247       case WhiteCapturesEnPassant:
5248       case BlackCapturesEnPassant:
5249         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5250                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5251         break;
5252     }
5253     SendToICS(user_move);
5254     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5255         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5256 }
5257
5258 void
5259 UploadGameEvent ()
5260 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5261     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5262     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5263     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5264       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5265       return;
5266     }
5267     if(gameMode != IcsExamining) { // is this ever not the case?
5268         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5269
5270         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5271           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5272         } else { // on FICS we must first go to general examine mode
5273           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5274         }
5275         if(gameInfo.variant != VariantNormal) {
5276             // try figure out wild number, as xboard names are not always valid on ICS
5277             for(i=1; i<=36; i++) {
5278               snprintf(buf, MSG_SIZ, "wild/%d", i);
5279                 if(StringToVariant(buf) == gameInfo.variant) break;
5280             }
5281             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5282             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5283             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5284         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5285         SendToICS(ics_prefix);
5286         SendToICS(buf);
5287         if(startedFromSetupPosition || backwardMostMove != 0) {
5288           fen = PositionToFEN(backwardMostMove, NULL, 1);
5289           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5290             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5291             SendToICS(buf);
5292           } else { // FICS: everything has to set by separate bsetup commands
5293             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5294             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5295             SendToICS(buf);
5296             if(!WhiteOnMove(backwardMostMove)) {
5297                 SendToICS("bsetup tomove black\n");
5298             }
5299             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5300             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5301             SendToICS(buf);
5302             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5303             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5304             SendToICS(buf);
5305             i = boards[backwardMostMove][EP_STATUS];
5306             if(i >= 0) { // set e.p.
5307               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5308                 SendToICS(buf);
5309             }
5310             bsetup++;
5311           }
5312         }
5313       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5314             SendToICS("bsetup done\n"); // switch to normal examining.
5315     }
5316     for(i = backwardMostMove; i<last; i++) {
5317         char buf[20];
5318         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5319         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5320             int len = strlen(moveList[i]);
5321             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5322             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5323         }
5324         SendToICS(buf);
5325     }
5326     SendToICS(ics_prefix);
5327     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5328 }
5329
5330 int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5331 int legNr = 1;
5332
5333 void
5334 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5335 {
5336     if (rf == DROP_RANK) {
5337       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5338       sprintf(move, "%c@%c%c\n",
5339                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5340     } else {
5341         if (promoChar == 'x' || promoChar == NULLCHAR) {
5342           sprintf(move, "%c%c%c%c\n",
5343                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5344           if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5345         } else {
5346             sprintf(move, "%c%c%c%c%c\n",
5347                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5348         }
5349     }
5350 }
5351
5352 void
5353 ProcessICSInitScript (FILE *f)
5354 {
5355     char buf[MSG_SIZ];
5356
5357     while (fgets(buf, MSG_SIZ, f)) {
5358         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5359     }
5360
5361     fclose(f);
5362 }
5363
5364
5365 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5366 int dragging;
5367 static ClickType lastClickType;
5368
5369 int
5370 Partner (ChessSquare *p)
5371 { // change piece into promotion partner if one shogi-promotes to the other
5372   int stride = gameInfo.variant == VariantChu ? 22 : 11;
5373   ChessSquare partner;
5374   partner = (*p/stride & 1 ? *p - stride : *p + stride);
5375   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5376   *p = partner;
5377   return 1;
5378 }
5379
5380 void
5381 Sweep (int step)
5382 {
5383     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5384     static int toggleFlag;
5385     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5386     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5387     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5388     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5389     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5390     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5391     do {
5392         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5393         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5394         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5395         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5396         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5397         if(!step) step = -1;
5398     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5399             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5400             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5401             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5402     if(toX >= 0) {
5403         int victim = boards[currentMove][toY][toX];
5404         boards[currentMove][toY][toX] = promoSweep;
5405         DrawPosition(FALSE, boards[currentMove]);
5406         boards[currentMove][toY][toX] = victim;
5407     } else
5408     ChangeDragPiece(promoSweep);
5409 }
5410
5411 int
5412 PromoScroll (int x, int y)
5413 {
5414   int step = 0;
5415
5416   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5417   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5418   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5419   if(!step) return FALSE;
5420   lastX = x; lastY = y;
5421   if((promoSweep < BlackPawn) == flipView) step = -step;
5422   if(step > 0) selectFlag = 1;
5423   if(!selectFlag) Sweep(step);
5424   return FALSE;
5425 }
5426
5427 void
5428 NextPiece (int step)
5429 {
5430     ChessSquare piece = boards[currentMove][toY][toX];
5431     do {
5432         pieceSweep -= step;
5433         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5434         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5435         if(!step) step = -1;
5436     } while(PieceToChar(pieceSweep) == '.');
5437     boards[currentMove][toY][toX] = pieceSweep;
5438     DrawPosition(FALSE, boards[currentMove]);
5439     boards[currentMove][toY][toX] = piece;
5440 }
5441 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5442 void
5443 AlphaRank (char *move, int n)
5444 {
5445 //    char *p = move, c; int x, y;
5446
5447     if (appData.debugMode) {
5448         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5449     }
5450
5451     if(move[1]=='*' &&
5452        move[2]>='0' && move[2]<='9' &&
5453        move[3]>='a' && move[3]<='x'    ) {
5454         move[1] = '@';
5455         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5456         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5457     } else
5458     if(move[0]>='0' && move[0]<='9' &&
5459        move[1]>='a' && move[1]<='x' &&
5460        move[2]>='0' && move[2]<='9' &&
5461        move[3]>='a' && move[3]<='x'    ) {
5462         /* input move, Shogi -> normal */
5463         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5464         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5465         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5466         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5467     } else
5468     if(move[1]=='@' &&
5469        move[3]>='0' && move[3]<='9' &&
5470        move[2]>='a' && move[2]<='x'    ) {
5471         move[1] = '*';
5472         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5473         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5474     } else
5475     if(
5476        move[0]>='a' && move[0]<='x' &&
5477        move[3]>='0' && move[3]<='9' &&
5478        move[2]>='a' && move[2]<='x'    ) {
5479          /* output move, normal -> Shogi */
5480         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5481         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5482         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5483         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5484         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5485     }
5486     if (appData.debugMode) {
5487         fprintf(debugFP, "   out = '%s'\n", move);
5488     }
5489 }
5490
5491 char yy_textstr[8000];
5492
5493 /* Parser for moves from gnuchess, ICS, or user typein box */
5494 Boolean
5495 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5496 {
5497     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5498
5499     switch (*moveType) {
5500       case WhitePromotion:
5501       case BlackPromotion:
5502       case WhiteNonPromotion:
5503       case BlackNonPromotion:
5504       case NormalMove:
5505       case FirstLeg:
5506       case WhiteCapturesEnPassant:
5507       case BlackCapturesEnPassant:
5508       case WhiteKingSideCastle:
5509       case WhiteQueenSideCastle:
5510       case BlackKingSideCastle:
5511       case BlackQueenSideCastle:
5512       case WhiteKingSideCastleWild:
5513       case WhiteQueenSideCastleWild:
5514       case BlackKingSideCastleWild:
5515       case BlackQueenSideCastleWild:
5516       /* Code added by Tord: */
5517       case WhiteHSideCastleFR:
5518       case WhiteASideCastleFR:
5519       case BlackHSideCastleFR:
5520       case BlackASideCastleFR:
5521       /* End of code added by Tord */
5522       case IllegalMove:         /* bug or odd chess variant */
5523         *fromX = currentMoveString[0] - AAA;
5524         *fromY = currentMoveString[1] - ONE;
5525         *toX = currentMoveString[2] - AAA;
5526         *toY = currentMoveString[3] - ONE;
5527         *promoChar = currentMoveString[4];
5528         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5529             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5530     if (appData.debugMode) {
5531         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5532     }
5533             *fromX = *fromY = *toX = *toY = 0;
5534             return FALSE;
5535         }
5536         if (appData.testLegality) {
5537           return (*moveType != IllegalMove);
5538         } else {
5539           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5540                          // [HGM] lion: if this is a double move we are less critical
5541                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5542         }
5543
5544       case WhiteDrop:
5545       case BlackDrop:
5546         *fromX = *moveType == WhiteDrop ?
5547           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5548           (int) CharToPiece(ToLower(currentMoveString[0]));
5549         *fromY = DROP_RANK;
5550         *toX = currentMoveString[2] - AAA;
5551         *toY = currentMoveString[3] - ONE;
5552         *promoChar = NULLCHAR;
5553         return TRUE;
5554
5555       case AmbiguousMove:
5556       case ImpossibleMove:
5557       case EndOfFile:
5558       case ElapsedTime:
5559       case Comment:
5560       case PGNTag:
5561       case NAG:
5562       case WhiteWins:
5563       case BlackWins:
5564       case GameIsDrawn:
5565       default:
5566     if (appData.debugMode) {
5567         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5568     }
5569         /* bug? */
5570         *fromX = *fromY = *toX = *toY = 0;
5571         *promoChar = NULLCHAR;
5572         return FALSE;
5573     }
5574 }
5575
5576 Boolean pushed = FALSE;
5577 char *lastParseAttempt;
5578
5579 void
5580 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5581 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5582   int fromX, fromY, toX, toY; char promoChar;
5583   ChessMove moveType;
5584   Boolean valid;
5585   int nr = 0;
5586
5587   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5588   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5589     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5590     pushed = TRUE;
5591   }
5592   endPV = forwardMostMove;
5593   do {
5594     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5595     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5596     lastParseAttempt = pv;
5597     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5598     if(!valid && nr == 0 &&
5599        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5600         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5601         // Hande case where played move is different from leading PV move
5602         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5603         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5604         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5605         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5606           endPV += 2; // if position different, keep this
5607           moveList[endPV-1][0] = fromX + AAA;
5608           moveList[endPV-1][1] = fromY + ONE;
5609           moveList[endPV-1][2] = toX + AAA;
5610           moveList[endPV-1][3] = toY + ONE;
5611           parseList[endPV-1][0] = NULLCHAR;
5612           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5613         }
5614       }
5615     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5616     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5617     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5618     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5619         valid++; // allow comments in PV
5620         continue;
5621     }
5622     nr++;
5623     if(endPV+1 > framePtr) break; // no space, truncate
5624     if(!valid) break;
5625     endPV++;
5626     CopyBoard(boards[endPV], boards[endPV-1]);
5627     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5628     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5629     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5630     CoordsToAlgebraic(boards[endPV - 1],
5631                              PosFlags(endPV - 1),
5632                              fromY, fromX, toY, toX, promoChar,
5633                              parseList[endPV - 1]);
5634   } while(valid);
5635   if(atEnd == 2) return; // used hidden, for PV conversion
5636   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5637   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5638   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5639                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5640   DrawPosition(TRUE, boards[currentMove]);
5641 }
5642
5643 int
5644 MultiPV (ChessProgramState *cps)
5645 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5646         int i;
5647         for(i=0; i<cps->nrOptions; i++)
5648             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5649                 return i;
5650         return -1;
5651 }
5652
5653 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5654
5655 Boolean
5656 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5657 {
5658         int startPV, multi, lineStart, origIndex = index;
5659         char *p, buf2[MSG_SIZ];
5660         ChessProgramState *cps = (pane ? &second : &first);
5661
5662         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5663         lastX = x; lastY = y;
5664         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5665         lineStart = startPV = index;
5666         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5667         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5668         index = startPV;
5669         do{ while(buf[index] && buf[index] != '\n') index++;
5670         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5671         buf[index] = 0;
5672         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
5673                 int n = cps->option[multi].value;
5674                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5675                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5676                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5677                 cps->option[multi].value = n;
5678                 *start = *end = 0;
5679                 return FALSE;
5680         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5681                 ExcludeClick(origIndex - lineStart);
5682                 return FALSE;
5683         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5684                 Collapse(origIndex - lineStart);
5685                 return FALSE;
5686         }
5687         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5688         *start = startPV; *end = index-1;
5689         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5690         return TRUE;
5691 }
5692
5693 char *
5694 PvToSAN (char *pv)
5695 {
5696         static char buf[10*MSG_SIZ];
5697         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5698         *buf = NULLCHAR;
5699         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5700         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5701         for(i = forwardMostMove; i<endPV; i++){
5702             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5703             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5704             k += strlen(buf+k);
5705         }
5706         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5707         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5708         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5709         endPV = savedEnd;
5710         return buf;
5711 }
5712
5713 Boolean
5714 LoadPV (int x, int y)
5715 { // called on right mouse click to load PV
5716   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5717   lastX = x; lastY = y;
5718   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5719   extendGame = FALSE;
5720   return TRUE;
5721 }
5722
5723 void
5724 UnLoadPV ()
5725 {
5726   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5727   if(endPV < 0) return;
5728   if(appData.autoCopyPV) CopyFENToClipboard();
5729   endPV = -1;
5730   if(extendGame && currentMove > forwardMostMove) {
5731         Boolean saveAnimate = appData.animate;
5732         if(pushed) {
5733             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5734                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5735             } else storedGames--; // abandon shelved tail of original game
5736         }
5737         pushed = FALSE;
5738         forwardMostMove = currentMove;
5739         currentMove = oldFMM;
5740         appData.animate = FALSE;
5741         ToNrEvent(forwardMostMove);
5742         appData.animate = saveAnimate;
5743   }
5744   currentMove = forwardMostMove;
5745   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5746   ClearPremoveHighlights();
5747   DrawPosition(TRUE, boards[currentMove]);
5748 }
5749
5750 void
5751 MovePV (int x, int y, int h)
5752 { // step through PV based on mouse coordinates (called on mouse move)
5753   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5754
5755   // we must somehow check if right button is still down (might be released off board!)
5756   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5757   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5758   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5759   if(!step) return;
5760   lastX = x; lastY = y;
5761
5762   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5763   if(endPV < 0) return;
5764   if(y < margin) step = 1; else
5765   if(y > h - margin) step = -1;
5766   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5767   currentMove += step;
5768   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5769   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5770                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5771   DrawPosition(FALSE, boards[currentMove]);
5772 }
5773
5774
5775 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5776 // All positions will have equal probability, but the current method will not provide a unique
5777 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5778 #define DARK 1
5779 #define LITE 2
5780 #define ANY 3
5781
5782 int squaresLeft[4];
5783 int piecesLeft[(int)BlackPawn];
5784 int seed, nrOfShuffles;
5785
5786 void
5787 GetPositionNumber ()
5788 {       // sets global variable seed
5789         int i;
5790
5791         seed = appData.defaultFrcPosition;
5792         if(seed < 0) { // randomize based on time for negative FRC position numbers
5793                 for(i=0; i<50; i++) seed += random();
5794                 seed = random() ^ random() >> 8 ^ random() << 8;
5795                 if(seed<0) seed = -seed;
5796         }
5797 }
5798
5799 int
5800 put (Board board, int pieceType, int rank, int n, int shade)
5801 // put the piece on the (n-1)-th empty squares of the given shade
5802 {
5803         int i;
5804
5805         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5806                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5807                         board[rank][i] = (ChessSquare) pieceType;
5808                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5809                         squaresLeft[ANY]--;
5810                         piecesLeft[pieceType]--;
5811                         return i;
5812                 }
5813         }
5814         return -1;
5815 }
5816
5817
5818 void
5819 AddOnePiece (Board board, int pieceType, int rank, int shade)
5820 // calculate where the next piece goes, (any empty square), and put it there
5821 {
5822         int i;
5823
5824         i = seed % squaresLeft[shade];
5825         nrOfShuffles *= squaresLeft[shade];
5826         seed /= squaresLeft[shade];
5827         put(board, pieceType, rank, i, shade);
5828 }
5829
5830 void
5831 AddTwoPieces (Board board, int pieceType, int rank)
5832 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5833 {
5834         int i, n=squaresLeft[ANY], j=n-1, k;
5835
5836         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5837         i = seed % k;  // pick one
5838         nrOfShuffles *= k;
5839         seed /= k;
5840         while(i >= j) i -= j--;
5841         j = n - 1 - j; i += j;
5842         put(board, pieceType, rank, j, ANY);
5843         put(board, pieceType, rank, i, ANY);
5844 }
5845
5846 void
5847 SetUpShuffle (Board board, int number)
5848 {
5849         int i, p, first=1;
5850
5851         GetPositionNumber(); nrOfShuffles = 1;
5852
5853         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5854         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5855         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5856
5857         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5858
5859         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5860             p = (int) board[0][i];
5861             if(p < (int) BlackPawn) piecesLeft[p] ++;
5862             board[0][i] = EmptySquare;
5863         }
5864
5865         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5866             // shuffles restricted to allow normal castling put KRR first
5867             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5868                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5869             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5870                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5871             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5872                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5873             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5874                 put(board, WhiteRook, 0, 0, ANY);
5875             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5876         }
5877
5878         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5879             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5880             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5881                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5882                 while(piecesLeft[p] >= 2) {
5883                     AddOnePiece(board, p, 0, LITE);
5884                     AddOnePiece(board, p, 0, DARK);
5885                 }
5886                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5887             }
5888
5889         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5890             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5891             // but we leave King and Rooks for last, to possibly obey FRC restriction
5892             if(p == (int)WhiteRook) continue;
5893             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5894             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5895         }
5896
5897         // now everything is placed, except perhaps King (Unicorn) and Rooks
5898
5899         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5900             // Last King gets castling rights
5901             while(piecesLeft[(int)WhiteUnicorn]) {
5902                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5903                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5904             }
5905
5906             while(piecesLeft[(int)WhiteKing]) {
5907                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5908                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5909             }
5910
5911
5912         } else {
5913             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5914             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5915         }
5916
5917         // Only Rooks can be left; simply place them all
5918         while(piecesLeft[(int)WhiteRook]) {
5919                 i = put(board, WhiteRook, 0, 0, ANY);
5920                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5921                         if(first) {
5922                                 first=0;
5923                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5924                         }
5925                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5926                 }
5927         }
5928         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5929             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5930         }
5931
5932         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5933 }
5934
5935 int
5936 SetCharTable (char *table, const char * map)
5937 /* [HGM] moved here from winboard.c because of its general usefulness */
5938 /*       Basically a safe strcpy that uses the last character as King */
5939 {
5940     int result = FALSE; int NrPieces;
5941
5942     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5943                     && NrPieces >= 12 && !(NrPieces&1)) {
5944         int i; /* [HGM] Accept even length from 12 to 34 */
5945
5946         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5947         for( i=0; i<NrPieces/2-1; i++ ) {
5948             table[i] = map[i];
5949             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5950         }
5951         table[(int) WhiteKing]  = map[NrPieces/2-1];
5952         table[(int) BlackKing]  = map[NrPieces-1];
5953
5954         result = TRUE;
5955     }
5956
5957     return result;
5958 }
5959
5960 void
5961 Prelude (Board board)
5962 {       // [HGM] superchess: random selection of exo-pieces
5963         int i, j, k; ChessSquare p;
5964         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5965
5966         GetPositionNumber(); // use FRC position number
5967
5968         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5969             SetCharTable(pieceToChar, appData.pieceToCharTable);
5970             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5971                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5972         }
5973
5974         j = seed%4;                 seed /= 4;
5975         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5976         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5977         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5978         j = seed%3 + (seed%3 >= j); seed /= 3;
5979         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5980         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5981         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5982         j = seed%3;                 seed /= 3;
5983         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5984         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5985         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5986         j = seed%2 + (seed%2 >= j); seed /= 2;
5987         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5988         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5989         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5990         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5991         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5992         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5993         put(board, exoPieces[0],    0, 0, ANY);
5994         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5995 }
5996
5997 void
5998 InitPosition (int redraw)
5999 {
6000     ChessSquare (* pieces)[BOARD_FILES];
6001     int i, j, pawnRow=1, pieceRows=1, overrule,
6002     oldx = gameInfo.boardWidth,
6003     oldy = gameInfo.boardHeight,
6004     oldh = gameInfo.holdingsWidth;
6005     static int oldv;
6006
6007     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6008
6009     /* [AS] Initialize pv info list [HGM] and game status */
6010     {
6011         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6012             pvInfoList[i].depth = 0;
6013             boards[i][EP_STATUS] = EP_NONE;
6014             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6015         }
6016
6017         initialRulePlies = 0; /* 50-move counter start */
6018
6019         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6020         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6021     }
6022
6023
6024     /* [HGM] logic here is completely changed. In stead of full positions */
6025     /* the initialized data only consist of the two backranks. The switch */
6026     /* selects which one we will use, which is than copied to the Board   */
6027     /* initialPosition, which for the rest is initialized by Pawns and    */
6028     /* empty squares. This initial position is then copied to boards[0],  */
6029     /* possibly after shuffling, so that it remains available.            */
6030
6031     gameInfo.holdingsWidth = 0; /* default board sizes */
6032     gameInfo.boardWidth    = 8;
6033     gameInfo.boardHeight   = 8;
6034     gameInfo.holdingsSize  = 0;
6035     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6036     for(i=0; i<BOARD_FILES-6; i++)
6037       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6038     initialPosition[EP_STATUS] = EP_NONE;
6039     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6040     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
6041     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6042          SetCharTable(pieceNickName, appData.pieceNickNames);
6043     else SetCharTable(pieceNickName, "............");
6044     pieces = FIDEArray;
6045
6046     switch (gameInfo.variant) {
6047     case VariantFischeRandom:
6048       shuffleOpenings = TRUE;
6049       appData.fischerCastling = TRUE;
6050     default:
6051       break;
6052     case VariantShatranj:
6053       pieces = ShatranjArray;
6054       nrCastlingRights = 0;
6055       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6056       break;
6057     case VariantMakruk:
6058       pieces = makrukArray;
6059       nrCastlingRights = 0;
6060       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6061       break;
6062     case VariantASEAN:
6063       pieces = aseanArray;
6064       nrCastlingRights = 0;
6065       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6066       break;
6067     case VariantTwoKings:
6068       pieces = twoKingsArray;
6069       break;
6070     case VariantGrand:
6071       pieces = GrandArray;
6072       nrCastlingRights = 0;
6073       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6074       gameInfo.boardWidth = 10;
6075       gameInfo.boardHeight = 10;
6076       gameInfo.holdingsSize = 7;
6077       break;
6078     case VariantCapaRandom:
6079       shuffleOpenings = TRUE;
6080       appData.fischerCastling = TRUE;
6081     case VariantCapablanca:
6082       pieces = CapablancaArray;
6083       gameInfo.boardWidth = 10;
6084       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6085       break;
6086     case VariantGothic:
6087       pieces = GothicArray;
6088       gameInfo.boardWidth = 10;
6089       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6090       break;
6091     case VariantSChess:
6092       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6093       gameInfo.holdingsSize = 7;
6094       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6095       break;
6096     case VariantJanus:
6097       pieces = JanusArray;
6098       gameInfo.boardWidth = 10;
6099       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6100       nrCastlingRights = 6;
6101         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6102         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6103         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6104         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6105         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6106         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6107       break;
6108     case VariantFalcon:
6109       pieces = FalconArray;
6110       gameInfo.boardWidth = 10;
6111       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6112       break;
6113     case VariantXiangqi:
6114       pieces = XiangqiArray;
6115       gameInfo.boardWidth  = 9;
6116       gameInfo.boardHeight = 10;
6117       nrCastlingRights = 0;
6118       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6119       break;
6120     case VariantShogi:
6121       pieces = ShogiArray;
6122       gameInfo.boardWidth  = 9;
6123       gameInfo.boardHeight = 9;
6124       gameInfo.holdingsSize = 7;
6125       nrCastlingRights = 0;
6126       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6127       break;
6128     case VariantChu:
6129       pieces = ChuArray; pieceRows = 3;
6130       gameInfo.boardWidth  = 12;
6131       gameInfo.boardHeight = 12;
6132       nrCastlingRights = 0;
6133       SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
6134                                 "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
6135       break;
6136     case VariantCourier:
6137       pieces = CourierArray;
6138       gameInfo.boardWidth  = 12;
6139       nrCastlingRights = 0;
6140       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6141       break;
6142     case VariantKnightmate:
6143       pieces = KnightmateArray;
6144       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6145       break;
6146     case VariantSpartan:
6147       pieces = SpartanArray;
6148       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6149       break;
6150     case VariantLion:
6151       pieces = lionArray;
6152       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6153       break;
6154     case VariantChuChess:
6155       pieces = ChuChessArray;
6156       gameInfo.boardWidth = 10;
6157       gameInfo.boardHeight = 10;
6158       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6159       break;
6160     case VariantFairy:
6161       pieces = fairyArray;
6162       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6163       break;
6164     case VariantGreat:
6165       pieces = GreatArray;
6166       gameInfo.boardWidth = 10;
6167       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6168       gameInfo.holdingsSize = 8;
6169       break;
6170     case VariantSuper:
6171       pieces = FIDEArray;
6172       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6173       gameInfo.holdingsSize = 8;
6174       startedFromSetupPosition = TRUE;
6175       break;
6176     case VariantCrazyhouse:
6177     case VariantBughouse:
6178       pieces = FIDEArray;
6179       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6180       gameInfo.holdingsSize = 5;
6181       break;
6182     case VariantWildCastle:
6183       pieces = FIDEArray;
6184       /* !!?shuffle with kings guaranteed to be on d or e file */
6185       shuffleOpenings = 1;
6186       break;
6187     case VariantNoCastle:
6188       pieces = FIDEArray;
6189       nrCastlingRights = 0;
6190       /* !!?unconstrained back-rank shuffle */
6191       shuffleOpenings = 1;
6192       break;
6193     }
6194
6195     overrule = 0;
6196     if(appData.NrFiles >= 0) {
6197         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6198         gameInfo.boardWidth = appData.NrFiles;
6199     }
6200     if(appData.NrRanks >= 0) {
6201         gameInfo.boardHeight = appData.NrRanks;
6202     }
6203     if(appData.holdingsSize >= 0) {
6204         i = appData.holdingsSize;
6205         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6206         gameInfo.holdingsSize = i;
6207     }
6208     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6209     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6210         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6211
6212     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6213     if(pawnRow < 1) pawnRow = 1;
6214     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6215        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6216     if(gameInfo.variant == VariantChu) pawnRow = 3;
6217
6218     /* User pieceToChar list overrules defaults */
6219     if(appData.pieceToCharTable != NULL)
6220         SetCharTable(pieceToChar, appData.pieceToCharTable);
6221
6222     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6223
6224         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6225             s = (ChessSquare) 0; /* account holding counts in guard band */
6226         for( i=0; i<BOARD_HEIGHT; i++ )
6227             initialPosition[i][j] = s;
6228
6229         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6230         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6231         initialPosition[pawnRow][j] = WhitePawn;
6232         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6233         if(gameInfo.variant == VariantXiangqi) {
6234             if(j&1) {
6235                 initialPosition[pawnRow][j] =
6236                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6237                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6238                    initialPosition[2][j] = WhiteCannon;
6239                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6240                 }
6241             }
6242         }
6243         if(gameInfo.variant == VariantChu) {
6244              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6245                initialPosition[pawnRow+1][j] = WhiteCobra,
6246                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6247              for(i=1; i<pieceRows; i++) {
6248                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6249                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6250              }
6251         }
6252         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6253             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6254                initialPosition[0][j] = WhiteRook;
6255                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6256             }
6257         }
6258         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6259     }
6260     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6261     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6262
6263             j=BOARD_LEFT+1;
6264             initialPosition[1][j] = WhiteBishop;
6265             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6266             j=BOARD_RGHT-2;
6267             initialPosition[1][j] = WhiteRook;
6268             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6269     }
6270
6271     if( nrCastlingRights == -1) {
6272         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6273         /*       This sets default castling rights from none to normal corners   */
6274         /* Variants with other castling rights must set them themselves above    */
6275         nrCastlingRights = 6;
6276
6277         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6278         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6279         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6280         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6281         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6282         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6283      }
6284
6285      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6286      if(gameInfo.variant == VariantGreat) { // promotion commoners
6287         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6288         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6289         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6290         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6291      }
6292      if( gameInfo.variant == VariantSChess ) {
6293       initialPosition[1][0] = BlackMarshall;
6294       initialPosition[2][0] = BlackAngel;
6295       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6296       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6297       initialPosition[1][1] = initialPosition[2][1] =
6298       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6299      }
6300   if (appData.debugMode) {
6301     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6302   }
6303     if(shuffleOpenings) {
6304         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6305         startedFromSetupPosition = TRUE;
6306     }
6307     if(startedFromPositionFile) {
6308       /* [HGM] loadPos: use PositionFile for every new game */
6309       CopyBoard(initialPosition, filePosition);
6310       for(i=0; i<nrCastlingRights; i++)
6311           initialRights[i] = filePosition[CASTLING][i];
6312       startedFromSetupPosition = TRUE;
6313     }
6314
6315     CopyBoard(boards[0], initialPosition);
6316
6317     if(oldx != gameInfo.boardWidth ||
6318        oldy != gameInfo.boardHeight ||
6319        oldv != gameInfo.variant ||
6320        oldh != gameInfo.holdingsWidth
6321                                          )
6322             InitDrawingSizes(-2 ,0);
6323
6324     oldv = gameInfo.variant;
6325     if (redraw)
6326       DrawPosition(TRUE, boards[currentMove]);
6327 }
6328
6329 void
6330 SendBoard (ChessProgramState *cps, int moveNum)
6331 {
6332     char message[MSG_SIZ];
6333
6334     if (cps->useSetboard) {
6335       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6336       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6337       SendToProgram(message, cps);
6338       free(fen);
6339
6340     } else {
6341       ChessSquare *bp;
6342       int i, j, left=0, right=BOARD_WIDTH;
6343       /* Kludge to set black to move, avoiding the troublesome and now
6344        * deprecated "black" command.
6345        */
6346       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6347         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6348
6349       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6350
6351       SendToProgram("edit\n", cps);
6352       SendToProgram("#\n", cps);
6353       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6354         bp = &boards[moveNum][i][left];
6355         for (j = left; j < right; j++, bp++) {
6356           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6357           if ((int) *bp < (int) BlackPawn) {
6358             if(j == BOARD_RGHT+1)
6359                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6360             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6361             if(message[0] == '+' || message[0] == '~') {
6362               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6363                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6364                         AAA + j, ONE + i);
6365             }
6366             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6367                 message[1] = BOARD_RGHT   - 1 - j + '1';
6368                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6369             }
6370             SendToProgram(message, cps);
6371           }
6372         }
6373       }
6374
6375       SendToProgram("c\n", cps);
6376       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6377         bp = &boards[moveNum][i][left];
6378         for (j = left; j < right; j++, bp++) {
6379           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6380           if (((int) *bp != (int) EmptySquare)
6381               && ((int) *bp >= (int) BlackPawn)) {
6382             if(j == BOARD_LEFT-2)
6383                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6384             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6385                     AAA + j, ONE + i);
6386             if(message[0] == '+' || message[0] == '~') {
6387               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6388                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6389                         AAA + j, ONE + i);
6390             }
6391             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6392                 message[1] = BOARD_RGHT   - 1 - j + '1';
6393                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6394             }
6395             SendToProgram(message, cps);
6396           }
6397         }
6398       }
6399
6400       SendToProgram(".\n", cps);
6401     }
6402     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6403 }
6404
6405 char exclusionHeader[MSG_SIZ];
6406 int exCnt, excludePtr;
6407 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6408 static Exclusion excluTab[200];
6409 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6410
6411 static void
6412 WriteMap (int s)
6413 {
6414     int j;
6415     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6416     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6417 }
6418
6419 static void
6420 ClearMap ()
6421 {
6422     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6423     excludePtr = 24; exCnt = 0;
6424     WriteMap(0);
6425 }
6426
6427 static void
6428 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6429 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6430     char buf[2*MOVE_LEN], *p;
6431     Exclusion *e = excluTab;
6432     int i;
6433     for(i=0; i<exCnt; i++)
6434         if(e[i].ff == fromX && e[i].fr == fromY &&
6435            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6436     if(i == exCnt) { // was not in exclude list; add it
6437         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6438         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6439             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6440             return; // abort
6441         }
6442         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6443         excludePtr++; e[i].mark = excludePtr++;
6444         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6445         exCnt++;
6446     }
6447     exclusionHeader[e[i].mark] = state;
6448 }
6449
6450 static int
6451 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6452 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6453     char buf[MSG_SIZ];
6454     int j, k;
6455     ChessMove moveType;
6456     if((signed char)promoChar == -1) { // kludge to indicate best move
6457         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6458             return 1; // if unparsable, abort
6459     }
6460     // update exclusion map (resolving toggle by consulting existing state)
6461     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6462     j = k%8; k >>= 3;
6463     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6464     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6465          excludeMap[k] |=   1<<j;
6466     else excludeMap[k] &= ~(1<<j);
6467     // update header
6468     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6469     // inform engine
6470     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6471     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6472     SendToBoth(buf);
6473     return (state == '+');
6474 }
6475
6476 static void
6477 ExcludeClick (int index)
6478 {
6479     int i, j;
6480     Exclusion *e = excluTab;
6481     if(index < 25) { // none, best or tail clicked
6482         if(index < 13) { // none: include all
6483             WriteMap(0); // clear map
6484             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6485             SendToBoth("include all\n"); // and inform engine
6486         } else if(index > 18) { // tail
6487             if(exclusionHeader[19] == '-') { // tail was excluded
6488                 SendToBoth("include all\n");
6489                 WriteMap(0); // clear map completely
6490                 // now re-exclude selected moves
6491                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6492                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6493             } else { // tail was included or in mixed state
6494                 SendToBoth("exclude all\n");
6495                 WriteMap(0xFF); // fill map completely
6496                 // now re-include selected moves
6497                 j = 0; // count them
6498                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6499                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6500                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6501             }
6502         } else { // best
6503             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6504         }
6505     } else {
6506         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6507             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6508             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6509             break;
6510         }
6511     }
6512 }
6513
6514 ChessSquare
6515 DefaultPromoChoice (int white)
6516 {
6517     ChessSquare result;
6518     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6519        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
6520         result = WhiteFerz; // no choice
6521     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6522         result= WhiteKing; // in Suicide Q is the last thing we want
6523     else if(gameInfo.variant == VariantSpartan)
6524         result = white ? WhiteQueen : WhiteAngel;
6525     else result = WhiteQueen;
6526     if(!white) result = WHITE_TO_BLACK result;
6527     return result;
6528 }
6529
6530 static int autoQueen; // [HGM] oneclick
6531
6532 int
6533 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6534 {
6535     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6536     /* [HGM] add Shogi promotions */
6537     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6538     ChessSquare piece, partner;
6539     ChessMove moveType;
6540     Boolean premove;
6541
6542     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6543     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6544
6545     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6546       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6547         return FALSE;
6548
6549     piece = boards[currentMove][fromY][fromX];
6550     if(gameInfo.variant == VariantChu) {
6551         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
6552         promotionZoneSize = BOARD_HEIGHT/3;
6553         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
6554     } else if(gameInfo.variant == VariantShogi) {
6555         promotionZoneSize = BOARD_HEIGHT/3;
6556         highestPromotingPiece = (int)WhiteAlfil;
6557     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6558         promotionZoneSize = 3;
6559     }
6560
6561     // Treat Lance as Pawn when it is not representing Amazon or Lance
6562     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6563         if(piece == WhiteLance) piece = WhitePawn; else
6564         if(piece == BlackLance) piece = BlackPawn;
6565     }
6566
6567     // next weed out all moves that do not touch the promotion zone at all
6568     if((int)piece >= BlackPawn) {
6569         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6570              return FALSE;
6571         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6572         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6573     } else {
6574         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6575            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6576         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6577              return FALSE;
6578     }
6579
6580     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6581
6582     // weed out mandatory Shogi promotions
6583     if(gameInfo.variant == VariantShogi) {
6584         if(piece >= BlackPawn) {
6585             if(toY == 0 && piece == BlackPawn ||
6586                toY == 0 && piece == BlackQueen ||
6587                toY <= 1 && piece == BlackKnight) {
6588                 *promoChoice = '+';
6589                 return FALSE;
6590             }
6591         } else {
6592             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6593                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6594                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6595                 *promoChoice = '+';
6596                 return FALSE;
6597             }
6598         }
6599     }
6600
6601     // weed out obviously illegal Pawn moves
6602     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6603         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6604         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6605         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6606         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6607         // note we are not allowed to test for valid (non-)capture, due to premove
6608     }
6609
6610     // we either have a choice what to promote to, or (in Shogi) whether to promote
6611     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6612        gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
6613         ChessSquare p=BlackFerz;  // no choice
6614         while(p < EmptySquare) {  //but make sure we use piece that exists
6615             *promoChoice = PieceToChar(p++);
6616             if(*promoChoice != '.') break;
6617         }
6618         return FALSE;
6619     }
6620     // no sense asking what we must promote to if it is going to explode...
6621     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6622         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6623         return FALSE;
6624     }
6625     // give caller the default choice even if we will not make it
6626     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6627     partner = piece; // pieces can promote if the pieceToCharTable says so
6628     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6629     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6630     if(        sweepSelect && gameInfo.variant != VariantGreat
6631                            && gameInfo.variant != VariantGrand
6632                            && gameInfo.variant != VariantSuper) return FALSE;
6633     if(autoQueen) return FALSE; // predetermined
6634
6635     // suppress promotion popup on illegal moves that are not premoves
6636     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6637               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6638     if(appData.testLegality && !premove) {
6639         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6640                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6641         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6642         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6643             return FALSE;
6644     }
6645
6646     return TRUE;
6647 }
6648
6649 int
6650 InPalace (int row, int column)
6651 {   /* [HGM] for Xiangqi */
6652     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6653          column < (BOARD_WIDTH + 4)/2 &&
6654          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6655     return FALSE;
6656 }
6657
6658 int
6659 PieceForSquare (int x, int y)
6660 {
6661   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6662      return -1;
6663   else
6664      return boards[currentMove][y][x];
6665 }
6666
6667 int
6668 OKToStartUserMove (int x, int y)
6669 {
6670     ChessSquare from_piece;
6671     int white_piece;
6672
6673     if (matchMode) return FALSE;
6674     if (gameMode == EditPosition) return TRUE;
6675
6676     if (x >= 0 && y >= 0)
6677       from_piece = boards[currentMove][y][x];
6678     else
6679       from_piece = EmptySquare;
6680
6681     if (from_piece == EmptySquare) return FALSE;
6682
6683     white_piece = (int)from_piece >= (int)WhitePawn &&
6684       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6685
6686     switch (gameMode) {
6687       case AnalyzeFile:
6688       case TwoMachinesPlay:
6689       case EndOfGame:
6690         return FALSE;
6691
6692       case IcsObserving:
6693       case IcsIdle:
6694         return FALSE;
6695
6696       case MachinePlaysWhite:
6697       case IcsPlayingBlack:
6698         if (appData.zippyPlay) return FALSE;
6699         if (white_piece) {
6700             DisplayMoveError(_("You are playing Black"));
6701             return FALSE;
6702         }
6703         break;
6704
6705       case MachinePlaysBlack:
6706       case IcsPlayingWhite:
6707         if (appData.zippyPlay) return FALSE;
6708         if (!white_piece) {
6709             DisplayMoveError(_("You are playing White"));
6710             return FALSE;
6711         }
6712         break;
6713
6714       case PlayFromGameFile:
6715             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6716       case EditGame:
6717         if (!white_piece && WhiteOnMove(currentMove)) {
6718             DisplayMoveError(_("It is White's turn"));
6719             return FALSE;
6720         }
6721         if (white_piece && !WhiteOnMove(currentMove)) {
6722             DisplayMoveError(_("It is Black's turn"));
6723             return FALSE;
6724         }
6725         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6726             /* Editing correspondence game history */
6727             /* Could disallow this or prompt for confirmation */
6728             cmailOldMove = -1;
6729         }
6730         break;
6731
6732       case BeginningOfGame:
6733         if (appData.icsActive) return FALSE;
6734         if (!appData.noChessProgram) {
6735             if (!white_piece) {
6736                 DisplayMoveError(_("You are playing White"));
6737                 return FALSE;
6738             }
6739         }
6740         break;
6741
6742       case Training:
6743         if (!white_piece && WhiteOnMove(currentMove)) {
6744             DisplayMoveError(_("It is White's turn"));
6745             return FALSE;
6746         }
6747         if (white_piece && !WhiteOnMove(currentMove)) {
6748             DisplayMoveError(_("It is Black's turn"));
6749             return FALSE;
6750         }
6751         break;
6752
6753       default:
6754       case IcsExamining:
6755         break;
6756     }
6757     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6758         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6759         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6760         && gameMode != AnalyzeFile && gameMode != Training) {
6761         DisplayMoveError(_("Displayed position is not current"));
6762         return FALSE;
6763     }
6764     return TRUE;
6765 }
6766
6767 Boolean
6768 OnlyMove (int *x, int *y, Boolean captures)
6769 {
6770     DisambiguateClosure cl;
6771     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6772     switch(gameMode) {
6773       case MachinePlaysBlack:
6774       case IcsPlayingWhite:
6775       case BeginningOfGame:
6776         if(!WhiteOnMove(currentMove)) return FALSE;
6777         break;
6778       case MachinePlaysWhite:
6779       case IcsPlayingBlack:
6780         if(WhiteOnMove(currentMove)) return FALSE;
6781         break;
6782       case EditGame:
6783         break;
6784       default:
6785         return FALSE;
6786     }
6787     cl.pieceIn = EmptySquare;
6788     cl.rfIn = *y;
6789     cl.ffIn = *x;
6790     cl.rtIn = -1;
6791     cl.ftIn = -1;
6792     cl.promoCharIn = NULLCHAR;
6793     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6794     if( cl.kind == NormalMove ||
6795         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6796         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6797         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6798       fromX = cl.ff;
6799       fromY = cl.rf;
6800       *x = cl.ft;
6801       *y = cl.rt;
6802       return TRUE;
6803     }
6804     if(cl.kind != ImpossibleMove) return FALSE;
6805     cl.pieceIn = EmptySquare;
6806     cl.rfIn = -1;
6807     cl.ffIn = -1;
6808     cl.rtIn = *y;
6809     cl.ftIn = *x;
6810     cl.promoCharIn = NULLCHAR;
6811     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6812     if( cl.kind == NormalMove ||
6813         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6814         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6815         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6816       fromX = cl.ff;
6817       fromY = cl.rf;
6818       *x = cl.ft;
6819       *y = cl.rt;
6820       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6821       return TRUE;
6822     }
6823     return FALSE;
6824 }
6825
6826 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6827 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6828 int lastLoadGameUseList = FALSE;
6829 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6830 ChessMove lastLoadGameStart = EndOfFile;
6831 int doubleClick;
6832 Boolean addToBookFlag;
6833
6834 void
6835 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6836 {
6837     ChessMove moveType;
6838     ChessSquare pup;
6839     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6840
6841     /* Check if the user is playing in turn.  This is complicated because we
6842        let the user "pick up" a piece before it is his turn.  So the piece he
6843        tried to pick up may have been captured by the time he puts it down!
6844        Therefore we use the color the user is supposed to be playing in this
6845        test, not the color of the piece that is currently on the starting
6846        square---except in EditGame mode, where the user is playing both
6847        sides; fortunately there the capture race can't happen.  (It can
6848        now happen in IcsExamining mode, but that's just too bad.  The user
6849        will get a somewhat confusing message in that case.)
6850        */
6851
6852     switch (gameMode) {
6853       case AnalyzeFile:
6854       case TwoMachinesPlay:
6855       case EndOfGame:
6856       case IcsObserving:
6857       case IcsIdle:
6858         /* We switched into a game mode where moves are not accepted,
6859            perhaps while the mouse button was down. */
6860         return;
6861
6862       case MachinePlaysWhite:
6863         /* User is moving for Black */
6864         if (WhiteOnMove(currentMove)) {
6865             DisplayMoveError(_("It is White's turn"));
6866             return;
6867         }
6868         break;
6869
6870       case MachinePlaysBlack:
6871         /* User is moving for White */
6872         if (!WhiteOnMove(currentMove)) {
6873             DisplayMoveError(_("It is Black's turn"));
6874             return;
6875         }
6876         break;
6877
6878       case PlayFromGameFile:
6879             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6880       case EditGame:
6881       case IcsExamining:
6882       case BeginningOfGame:
6883       case AnalyzeMode:
6884       case Training:
6885         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6886         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6887             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6888             /* User is moving for Black */
6889             if (WhiteOnMove(currentMove)) {
6890                 DisplayMoveError(_("It is White's turn"));
6891                 return;
6892             }
6893         } else {
6894             /* User is moving for White */
6895             if (!WhiteOnMove(currentMove)) {
6896                 DisplayMoveError(_("It is Black's turn"));
6897                 return;
6898             }
6899         }
6900         break;
6901
6902       case IcsPlayingBlack:
6903         /* User is moving for Black */
6904         if (WhiteOnMove(currentMove)) {
6905             if (!appData.premove) {
6906                 DisplayMoveError(_("It is White's turn"));
6907             } else if (toX >= 0 && toY >= 0) {
6908                 premoveToX = toX;
6909                 premoveToY = toY;
6910                 premoveFromX = fromX;
6911                 premoveFromY = fromY;
6912                 premovePromoChar = promoChar;
6913                 gotPremove = 1;
6914                 if (appData.debugMode)
6915                     fprintf(debugFP, "Got premove: fromX %d,"
6916                             "fromY %d, toX %d, toY %d\n",
6917                             fromX, fromY, toX, toY);
6918             }
6919             return;
6920         }
6921         break;
6922
6923       case IcsPlayingWhite:
6924         /* User is moving for White */
6925         if (!WhiteOnMove(currentMove)) {
6926             if (!appData.premove) {
6927                 DisplayMoveError(_("It is Black's turn"));
6928             } else if (toX >= 0 && toY >= 0) {
6929                 premoveToX = toX;
6930                 premoveToY = toY;
6931                 premoveFromX = fromX;
6932                 premoveFromY = fromY;
6933                 premovePromoChar = promoChar;
6934                 gotPremove = 1;
6935                 if (appData.debugMode)
6936                     fprintf(debugFP, "Got premove: fromX %d,"
6937                             "fromY %d, toX %d, toY %d\n",
6938                             fromX, fromY, toX, toY);
6939             }
6940             return;
6941         }
6942         break;
6943
6944       default:
6945         break;
6946
6947       case EditPosition:
6948         /* EditPosition, empty square, or different color piece;
6949            click-click move is possible */
6950         if (toX == -2 || toY == -2) {
6951             boards[0][fromY][fromX] = EmptySquare;
6952             DrawPosition(FALSE, boards[currentMove]);
6953             return;
6954         } else if (toX >= 0 && toY >= 0) {
6955             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
6956                 ChessSquare q, p = boards[0][rf][ff];
6957                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
6958                 if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
6959                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
6960                 if(PieceToChar(q) == '+') gatingPiece = p;
6961             }
6962             boards[0][toY][toX] = boards[0][fromY][fromX];
6963             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6964                 if(boards[0][fromY][0] != EmptySquare) {
6965                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6966                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6967                 }
6968             } else
6969             if(fromX == BOARD_RGHT+1) {
6970                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6971                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6972                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6973                 }
6974             } else
6975             boards[0][fromY][fromX] = gatingPiece;
6976             DrawPosition(FALSE, boards[currentMove]);
6977             return;
6978         }
6979         return;
6980     }
6981
6982     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
6983     pup = boards[currentMove][toY][toX];
6984
6985     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6986     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6987          if( pup != EmptySquare ) return;
6988          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6989            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6990                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6991            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6992            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6993            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6994            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6995          fromY = DROP_RANK;
6996     }
6997
6998     /* [HGM] always test for legality, to get promotion info */
6999     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7000                                          fromY, fromX, toY, toX, promoChar);
7001
7002     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7003
7004     /* [HGM] but possibly ignore an IllegalMove result */
7005     if (appData.testLegality) {
7006         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7007             DisplayMoveError(_("Illegal move"));
7008             return;
7009         }
7010     }
7011
7012     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7013         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7014              ClearPremoveHighlights(); // was included
7015         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7016         return;
7017     }
7018
7019     if(addToBookFlag) { // adding moves to book
7020         char buf[MSG_SIZ], move[MSG_SIZ];
7021         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7022         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7023         AddBookMove(buf);
7024         addToBookFlag = FALSE;
7025         ClearHighlights();
7026         return;
7027     }
7028
7029     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7030 }
7031
7032 /* Common tail of UserMoveEvent and DropMenuEvent */
7033 int
7034 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7035 {
7036     char *bookHit = 0;
7037
7038     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7039         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7040         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7041         if(WhiteOnMove(currentMove)) {
7042             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7043         } else {
7044             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7045         }
7046     }
7047
7048     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7049        move type in caller when we know the move is a legal promotion */
7050     if(moveType == NormalMove && promoChar)
7051         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7052
7053     /* [HGM] <popupFix> The following if has been moved here from
7054        UserMoveEvent(). Because it seemed to belong here (why not allow
7055        piece drops in training games?), and because it can only be
7056        performed after it is known to what we promote. */
7057     if (gameMode == Training) {
7058       /* compare the move played on the board to the next move in the
7059        * game. If they match, display the move and the opponent's response.
7060        * If they don't match, display an error message.
7061        */
7062       int saveAnimate;
7063       Board testBoard;
7064       CopyBoard(testBoard, boards[currentMove]);
7065       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7066
7067       if (CompareBoards(testBoard, boards[currentMove+1])) {
7068         ForwardInner(currentMove+1);
7069
7070         /* Autoplay the opponent's response.
7071          * if appData.animate was TRUE when Training mode was entered,
7072          * the response will be animated.
7073          */
7074         saveAnimate = appData.animate;
7075         appData.animate = animateTraining;
7076         ForwardInner(currentMove+1);
7077         appData.animate = saveAnimate;
7078
7079         /* check for the end of the game */
7080         if (currentMove >= forwardMostMove) {
7081           gameMode = PlayFromGameFile;
7082           ModeHighlight();
7083           SetTrainingModeOff();
7084           DisplayInformation(_("End of game"));
7085         }
7086       } else {
7087         DisplayError(_("Incorrect move"), 0);
7088       }
7089       return 1;
7090     }
7091
7092   /* Ok, now we know that the move is good, so we can kill
7093      the previous line in Analysis Mode */
7094   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7095                                 && currentMove < forwardMostMove) {
7096     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7097     else forwardMostMove = currentMove;
7098   }
7099
7100   ClearMap();
7101
7102   /* If we need the chess program but it's dead, restart it */
7103   ResurrectChessProgram();
7104
7105   /* A user move restarts a paused game*/
7106   if (pausing)
7107     PauseEvent();
7108
7109   thinkOutput[0] = NULLCHAR;
7110
7111   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7112
7113   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7114     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7115     return 1;
7116   }
7117
7118   if (gameMode == BeginningOfGame) {
7119     if (appData.noChessProgram) {
7120       gameMode = EditGame;
7121       SetGameInfo();
7122     } else {
7123       char buf[MSG_SIZ];
7124       gameMode = MachinePlaysBlack;
7125       StartClocks();
7126       SetGameInfo();
7127       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7128       DisplayTitle(buf);
7129       if (first.sendName) {
7130         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7131         SendToProgram(buf, &first);
7132       }
7133       StartClocks();
7134     }
7135     ModeHighlight();
7136   }
7137
7138   /* Relay move to ICS or chess engine */
7139   if (appData.icsActive) {
7140     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7141         gameMode == IcsExamining) {
7142       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7143         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7144         SendToICS("draw ");
7145         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7146       }
7147       // also send plain move, in case ICS does not understand atomic claims
7148       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7149       ics_user_moved = 1;
7150     }
7151   } else {
7152     if (first.sendTime && (gameMode == BeginningOfGame ||
7153                            gameMode == MachinePlaysWhite ||
7154                            gameMode == MachinePlaysBlack)) {
7155       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7156     }
7157     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7158          // [HGM] book: if program might be playing, let it use book
7159         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7160         first.maybeThinking = TRUE;
7161     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7162         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7163         SendBoard(&first, currentMove+1);
7164         if(second.analyzing) {
7165             if(!second.useSetboard) SendToProgram("undo\n", &second);
7166             SendBoard(&second, currentMove+1);
7167         }
7168     } else {
7169         SendMoveToProgram(forwardMostMove-1, &first);
7170         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7171     }
7172     if (currentMove == cmailOldMove + 1) {
7173       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7174     }
7175   }
7176
7177   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7178
7179   switch (gameMode) {
7180   case EditGame:
7181     if(appData.testLegality)
7182     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7183     case MT_NONE:
7184     case MT_CHECK:
7185       break;
7186     case MT_CHECKMATE:
7187     case MT_STAINMATE:
7188       if (WhiteOnMove(currentMove)) {
7189         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7190       } else {
7191         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7192       }
7193       break;
7194     case MT_STALEMATE:
7195       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7196       break;
7197     }
7198     break;
7199
7200   case MachinePlaysBlack:
7201   case MachinePlaysWhite:
7202     /* disable certain menu options while machine is thinking */
7203     SetMachineThinkingEnables();
7204     break;
7205
7206   default:
7207     break;
7208   }
7209
7210   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7211   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7212
7213   if(bookHit) { // [HGM] book: simulate book reply
7214         static char bookMove[MSG_SIZ]; // a bit generous?
7215
7216         programStats.nodes = programStats.depth = programStats.time =
7217         programStats.score = programStats.got_only_move = 0;
7218         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7219
7220         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7221         strcat(bookMove, bookHit);
7222         HandleMachineMove(bookMove, &first);
7223   }
7224   return 1;
7225 }
7226
7227 void
7228 MarkByFEN(char *fen)
7229 {
7230         int r, f;
7231         if(!appData.markers || !appData.highlightDragging) return;
7232         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7233         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7234         while(*fen) {
7235             int s = 0;
7236             marker[r][f] = 0;
7237             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7238             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
7239             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7240             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7241             if(*fen == 'T') marker[r][f++] = 0; else
7242             if(*fen == 'Y') marker[r][f++] = 1; else
7243             if(*fen == 'G') marker[r][f++] = 3; else
7244             if(*fen == 'B') marker[r][f++] = 4; else
7245             if(*fen == 'C') marker[r][f++] = 5; else
7246             if(*fen == 'M') marker[r][f++] = 6; else
7247             if(*fen == 'W') marker[r][f++] = 7; else
7248             if(*fen == 'D') marker[r][f++] = 8; else
7249             if(*fen == 'R') marker[r][f++] = 2; else {
7250                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7251               f += s; fen -= s>0;
7252             }
7253             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7254             if(r < 0) break;
7255             fen++;
7256         }
7257         DrawPosition(TRUE, NULL);
7258 }
7259
7260 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7261
7262 void
7263 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7264 {
7265     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7266     Markers *m = (Markers *) closure;
7267     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7268         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7269                          || kind == WhiteCapturesEnPassant
7270                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
7271     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
7272 }
7273
7274 static int hoverSavedValid;
7275
7276 void
7277 MarkTargetSquares (int clear)
7278 {
7279   int x, y, sum=0;
7280   if(clear) { // no reason to ever suppress clearing
7281     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7282     hoverSavedValid = 0;
7283     if(!sum) return; // nothing was cleared,no redraw needed
7284   } else {
7285     int capt = 0;
7286     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7287        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7288     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7289     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7290       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7291       if(capt)
7292       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7293     }
7294   }
7295   DrawPosition(FALSE, NULL);
7296 }
7297
7298 int
7299 Explode (Board board, int fromX, int fromY, int toX, int toY)
7300 {
7301     if(gameInfo.variant == VariantAtomic &&
7302        (board[toY][toX] != EmptySquare ||                     // capture?
7303         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7304                          board[fromY][fromX] == BlackPawn   )
7305       )) {
7306         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7307         return TRUE;
7308     }
7309     return FALSE;
7310 }
7311
7312 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7313
7314 int
7315 CanPromote (ChessSquare piece, int y)
7316 {
7317         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7318         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7319         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7320         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7321            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7322            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7323          gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
7324         return (piece == BlackPawn && y <= zone ||
7325                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7326                 piece == BlackLance && y == 1 ||
7327                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
7328 }
7329
7330 void
7331 HoverEvent (int xPix, int yPix, int x, int y)
7332 {
7333         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7334         int r, f;
7335         if(!first.highlight) return;
7336         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7337         if(x == oldX && y == oldY) return; // only do something if we enter new square
7338         oldFromX = fromX; oldFromY = fromY;
7339         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7340           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7341             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7342           hoverSavedValid = 1;
7343         } else if(oldX != x || oldY != y) {
7344           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7345           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7346           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7347             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7348           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7349             char buf[MSG_SIZ];
7350             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7351             SendToProgram(buf, &first);
7352           }
7353           oldX = x; oldY = y;
7354 //        SetHighlights(fromX, fromY, x, y);
7355         }
7356 }
7357
7358 void ReportClick(char *action, int x, int y)
7359 {
7360         char buf[MSG_SIZ]; // Inform engine of what user does
7361         int r, f;
7362         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7363           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7364             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7365         if(!first.highlight || gameMode == EditPosition) return;
7366         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7367         SendToProgram(buf, &first);
7368 }
7369
7370 void
7371 LeftClick (ClickType clickType, int xPix, int yPix)
7372 {
7373     int x, y;
7374     Boolean saveAnimate;
7375     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7376     char promoChoice = NULLCHAR;
7377     ChessSquare piece;
7378     static TimeMark lastClickTime, prevClickTime;
7379
7380     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7381
7382     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7383
7384     if (clickType == Press) ErrorPopDown();
7385     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7386
7387     x = EventToSquare(xPix, BOARD_WIDTH);
7388     y = EventToSquare(yPix, BOARD_HEIGHT);
7389     if (!flipView && y >= 0) {
7390         y = BOARD_HEIGHT - 1 - y;
7391     }
7392     if (flipView && x >= 0) {
7393         x = BOARD_WIDTH - 1 - x;
7394     }
7395
7396     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7397         defaultPromoChoice = promoSweep;
7398         promoSweep = EmptySquare;   // terminate sweep
7399         promoDefaultAltered = TRUE;
7400         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7401     }
7402
7403     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7404         if(clickType == Release) return; // ignore upclick of click-click destination
7405         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7406         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7407         if(gameInfo.holdingsWidth &&
7408                 (WhiteOnMove(currentMove)
7409                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7410                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7411             // click in right holdings, for determining promotion piece
7412             ChessSquare p = boards[currentMove][y][x];
7413             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7414             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7415             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7416                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7417                 fromX = fromY = -1;
7418                 return;
7419             }
7420         }
7421         DrawPosition(FALSE, boards[currentMove]);
7422         return;
7423     }
7424
7425     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7426     if(clickType == Press
7427             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7428               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7429               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7430         return;
7431
7432     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7433         // could be static click on premove from-square: abort premove
7434         gotPremove = 0;
7435         ClearPremoveHighlights();
7436     }
7437
7438     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7439         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7440
7441     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7442         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7443                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7444         defaultPromoChoice = DefaultPromoChoice(side);
7445     }
7446
7447     autoQueen = appData.alwaysPromoteToQueen;
7448
7449     if (fromX == -1) {
7450       int originalY = y;
7451       gatingPiece = EmptySquare;
7452       if (clickType != Press) {
7453         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7454             DragPieceEnd(xPix, yPix); dragging = 0;
7455             DrawPosition(FALSE, NULL);
7456         }
7457         return;
7458       }
7459       doubleClick = FALSE;
7460       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7461         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7462       }
7463       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7464       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7465          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7466          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7467             /* First square */
7468             if (OKToStartUserMove(fromX, fromY)) {
7469                 second = 0;
7470                 ReportClick("lift", x, y);
7471                 MarkTargetSquares(0);
7472                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7473                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7474                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7475                     promoSweep = defaultPromoChoice;
7476                     selectFlag = 0; lastX = xPix; lastY = yPix;
7477                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7478                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7479                 }
7480                 if (appData.highlightDragging) {
7481                     SetHighlights(fromX, fromY, -1, -1);
7482                 } else {
7483                     ClearHighlights();
7484                 }
7485             } else fromX = fromY = -1;
7486             return;
7487         }
7488     }
7489
7490     /* fromX != -1 */
7491     if (clickType == Press && gameMode != EditPosition) {
7492         ChessSquare fromP;
7493         ChessSquare toP;
7494         int frc;
7495
7496         // ignore off-board to clicks
7497         if(y < 0 || x < 0) return;
7498
7499         /* Check if clicking again on the same color piece */
7500         fromP = boards[currentMove][fromY][fromX];
7501         toP = boards[currentMove][y][x];
7502         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7503         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7504             legal[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7505            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7506              WhitePawn <= toP && toP <= WhiteKing &&
7507              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7508              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7509             (BlackPawn <= fromP && fromP <= BlackKing &&
7510              BlackPawn <= toP && toP <= BlackKing &&
7511              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7512              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7513             /* Clicked again on same color piece -- changed his mind */
7514             second = (x == fromX && y == fromY);
7515             killX = killY = -1;
7516             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7517                 second = FALSE; // first double-click rather than scond click
7518                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7519             }
7520             promoDefaultAltered = FALSE;
7521             MarkTargetSquares(1);
7522            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7523             if (appData.highlightDragging) {
7524                 SetHighlights(x, y, -1, -1);
7525             } else {
7526                 ClearHighlights();
7527             }
7528             if (OKToStartUserMove(x, y)) {
7529                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7530                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7531                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7532                  gatingPiece = boards[currentMove][fromY][fromX];
7533                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7534                 fromX = x;
7535                 fromY = y; dragging = 1;
7536                 ReportClick("lift", x, y);
7537                 MarkTargetSquares(0);
7538                 DragPieceBegin(xPix, yPix, FALSE);
7539                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7540                     promoSweep = defaultPromoChoice;
7541                     selectFlag = 0; lastX = xPix; lastY = yPix;
7542                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7543                 }
7544             }
7545            }
7546            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7547            second = FALSE;
7548         }
7549         // ignore clicks on holdings
7550         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7551     }
7552
7553     if (clickType == Release && x == fromX && y == fromY && killX < 0) {
7554         DragPieceEnd(xPix, yPix); dragging = 0;
7555         if(clearFlag) {
7556             // a deferred attempt to click-click move an empty square on top of a piece
7557             boards[currentMove][y][x] = EmptySquare;
7558             ClearHighlights();
7559             DrawPosition(FALSE, boards[currentMove]);
7560             fromX = fromY = -1; clearFlag = 0;
7561             return;
7562         }
7563         if (appData.animateDragging) {
7564             /* Undo animation damage if any */
7565             DrawPosition(FALSE, NULL);
7566         }
7567         if (second || sweepSelecting) {
7568             /* Second up/down in same square; just abort move */
7569             if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
7570             second = sweepSelecting = 0;
7571             fromX = fromY = -1;
7572             gatingPiece = EmptySquare;
7573             MarkTargetSquares(1);
7574             ClearHighlights();
7575             gotPremove = 0;
7576             ClearPremoveHighlights();
7577         } else {
7578             /* First upclick in same square; start click-click mode */
7579             SetHighlights(x, y, -1, -1);
7580         }
7581         return;
7582     }
7583
7584     clearFlag = 0;
7585
7586     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7587        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7588         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7589         DisplayMessage(_("only marked squares are legal"),"");
7590         DrawPosition(TRUE, NULL);
7591         return; // ignore to-click
7592     }
7593
7594     /* we now have a different from- and (possibly off-board) to-square */
7595     /* Completed move */
7596     if(!sweepSelecting) {
7597         toX = x;
7598         toY = y;
7599     }
7600
7601     piece = boards[currentMove][fromY][fromX];
7602
7603     saveAnimate = appData.animate;
7604     if (clickType == Press) {
7605         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7606         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7607             // must be Edit Position mode with empty-square selected
7608             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7609             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7610             return;
7611         }
7612         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7613             return;
7614         }
7615         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7616             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7617         } else
7618         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7619         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7620           if(appData.sweepSelect) {
7621             promoSweep = defaultPromoChoice;
7622             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
7623             selectFlag = 0; lastX = xPix; lastY = yPix;
7624             Sweep(0); // Pawn that is going to promote: preview promotion piece
7625             sweepSelecting = 1;
7626             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7627             MarkTargetSquares(1);
7628           }
7629           return; // promo popup appears on up-click
7630         }
7631         /* Finish clickclick move */
7632         if (appData.animate || appData.highlightLastMove) {
7633             SetHighlights(fromX, fromY, toX, toY);
7634         } else {
7635             ClearHighlights();
7636         }
7637     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7638         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7639         if (appData.animate || appData.highlightLastMove) {
7640             SetHighlights(fromX, fromY, toX, toY);
7641         } else {
7642             ClearHighlights();
7643         }
7644     } else {
7645 #if 0
7646 // [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
7647         /* Finish drag move */
7648         if (appData.highlightLastMove) {
7649             SetHighlights(fromX, fromY, toX, toY);
7650         } else {
7651             ClearHighlights();
7652         }
7653 #endif
7654         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7655         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7656           dragging *= 2;            // flag button-less dragging if we are dragging
7657           MarkTargetSquares(1);
7658           if(x == killX && y == killY) killX = killY = -1; else {
7659             killX = x; killY = y;     //remeber this square as intermediate
7660             ReportClick("put", x, y); // and inform engine
7661             ReportClick("lift", x, y);
7662             MarkTargetSquares(0);
7663             return;
7664           }
7665         }
7666         DragPieceEnd(xPix, yPix); dragging = 0;
7667         /* Don't animate move and drag both */
7668         appData.animate = FALSE;
7669     }
7670
7671     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7672     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7673         ChessSquare piece = boards[currentMove][fromY][fromX];
7674         if(gameMode == EditPosition && piece != EmptySquare &&
7675            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7676             int n;
7677
7678             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7679                 n = PieceToNumber(piece - (int)BlackPawn);
7680                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7681                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7682                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7683             } else
7684             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7685                 n = PieceToNumber(piece);
7686                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7687                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7688                 boards[currentMove][n][BOARD_WIDTH-2]++;
7689             }
7690             boards[currentMove][fromY][fromX] = EmptySquare;
7691         }
7692         ClearHighlights();
7693         fromX = fromY = -1;
7694         MarkTargetSquares(1);
7695         DrawPosition(TRUE, boards[currentMove]);
7696         return;
7697     }
7698
7699     // off-board moves should not be highlighted
7700     if(x < 0 || y < 0) ClearHighlights();
7701     else ReportClick("put", x, y);
7702
7703     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7704
7705     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7706         SetHighlights(fromX, fromY, toX, toY);
7707         MarkTargetSquares(1);
7708         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7709             // [HGM] super: promotion to captured piece selected from holdings
7710             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7711             promotionChoice = TRUE;
7712             // kludge follows to temporarily execute move on display, without promoting yet
7713             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7714             boards[currentMove][toY][toX] = p;
7715             DrawPosition(FALSE, boards[currentMove]);
7716             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7717             boards[currentMove][toY][toX] = q;
7718             DisplayMessage("Click in holdings to choose piece", "");
7719             return;
7720         }
7721         PromotionPopUp(promoChoice);
7722     } else {
7723         int oldMove = currentMove;
7724         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7725         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7726         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7727         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7728            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7729             DrawPosition(TRUE, boards[currentMove]);
7730         MarkTargetSquares(1);
7731         fromX = fromY = -1;
7732     }
7733     appData.animate = saveAnimate;
7734     if (appData.animate || appData.animateDragging) {
7735         /* Undo animation damage if needed */
7736         DrawPosition(FALSE, NULL);
7737     }
7738 }
7739
7740 int
7741 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7742 {   // front-end-free part taken out of PieceMenuPopup
7743     int whichMenu; int xSqr, ySqr;
7744
7745     if(seekGraphUp) { // [HGM] seekgraph
7746         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7747         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7748         return -2;
7749     }
7750
7751     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7752          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7753         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7754         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7755         if(action == Press)   {
7756             originalFlip = flipView;
7757             flipView = !flipView; // temporarily flip board to see game from partners perspective
7758             DrawPosition(TRUE, partnerBoard);
7759             DisplayMessage(partnerStatus, "");
7760             partnerUp = TRUE;
7761         } else if(action == Release) {
7762             flipView = originalFlip;
7763             DrawPosition(TRUE, boards[currentMove]);
7764             partnerUp = FALSE;
7765         }
7766         return -2;
7767     }
7768
7769     xSqr = EventToSquare(x, BOARD_WIDTH);
7770     ySqr = EventToSquare(y, BOARD_HEIGHT);
7771     if (action == Release) {
7772         if(pieceSweep != EmptySquare) {
7773             EditPositionMenuEvent(pieceSweep, toX, toY);
7774             pieceSweep = EmptySquare;
7775         } else UnLoadPV(); // [HGM] pv
7776     }
7777     if (action != Press) return -2; // return code to be ignored
7778     switch (gameMode) {
7779       case IcsExamining:
7780         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7781       case EditPosition:
7782         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7783         if (xSqr < 0 || ySqr < 0) return -1;
7784         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7785         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7786         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7787         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7788         NextPiece(0);
7789         return 2; // grab
7790       case IcsObserving:
7791         if(!appData.icsEngineAnalyze) return -1;
7792       case IcsPlayingWhite:
7793       case IcsPlayingBlack:
7794         if(!appData.zippyPlay) goto noZip;
7795       case AnalyzeMode:
7796       case AnalyzeFile:
7797       case MachinePlaysWhite:
7798       case MachinePlaysBlack:
7799       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7800         if (!appData.dropMenu) {
7801           LoadPV(x, y);
7802           return 2; // flag front-end to grab mouse events
7803         }
7804         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7805            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7806       case EditGame:
7807       noZip:
7808         if (xSqr < 0 || ySqr < 0) return -1;
7809         if (!appData.dropMenu || appData.testLegality &&
7810             gameInfo.variant != VariantBughouse &&
7811             gameInfo.variant != VariantCrazyhouse) return -1;
7812         whichMenu = 1; // drop menu
7813         break;
7814       default:
7815         return -1;
7816     }
7817
7818     if (((*fromX = xSqr) < 0) ||
7819         ((*fromY = ySqr) < 0)) {
7820         *fromX = *fromY = -1;
7821         return -1;
7822     }
7823     if (flipView)
7824       *fromX = BOARD_WIDTH - 1 - *fromX;
7825     else
7826       *fromY = BOARD_HEIGHT - 1 - *fromY;
7827
7828     return whichMenu;
7829 }
7830
7831 void
7832 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7833 {
7834 //    char * hint = lastHint;
7835     FrontEndProgramStats stats;
7836
7837     stats.which = cps == &first ? 0 : 1;
7838     stats.depth = cpstats->depth;
7839     stats.nodes = cpstats->nodes;
7840     stats.score = cpstats->score;
7841     stats.time = cpstats->time;
7842     stats.pv = cpstats->movelist;
7843     stats.hint = lastHint;
7844     stats.an_move_index = 0;
7845     stats.an_move_count = 0;
7846
7847     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7848         stats.hint = cpstats->move_name;
7849         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7850         stats.an_move_count = cpstats->nr_moves;
7851     }
7852
7853     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
7854
7855     SetProgramStats( &stats );
7856 }
7857
7858 void
7859 ClearEngineOutputPane (int which)
7860 {
7861     static FrontEndProgramStats dummyStats;
7862     dummyStats.which = which;
7863     dummyStats.pv = "#";
7864     SetProgramStats( &dummyStats );
7865 }
7866
7867 #define MAXPLAYERS 500
7868
7869 char *
7870 TourneyStandings (int display)
7871 {
7872     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7873     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7874     char result, *p, *names[MAXPLAYERS];
7875
7876     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7877         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7878     names[0] = p = strdup(appData.participants);
7879     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7880
7881     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7882
7883     while(result = appData.results[nr]) {
7884         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7885         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7886         wScore = bScore = 0;
7887         switch(result) {
7888           case '+': wScore = 2; break;
7889           case '-': bScore = 2; break;
7890           case '=': wScore = bScore = 1; break;
7891           case ' ':
7892           case '*': return strdup("busy"); // tourney not finished
7893         }
7894         score[w] += wScore;
7895         score[b] += bScore;
7896         games[w]++;
7897         games[b]++;
7898         nr++;
7899     }
7900     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7901     for(w=0; w<nPlayers; w++) {
7902         bScore = -1;
7903         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7904         ranking[w] = b; points[w] = bScore; score[b] = -2;
7905     }
7906     p = malloc(nPlayers*34+1);
7907     for(w=0; w<nPlayers && w<display; w++)
7908         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7909     free(names[0]);
7910     return p;
7911 }
7912
7913 void
7914 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7915 {       // count all piece types
7916         int p, f, r;
7917         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7918         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7919         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7920                 p = board[r][f];
7921                 pCnt[p]++;
7922                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7923                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7924                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7925                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7926                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7927                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7928         }
7929 }
7930
7931 int
7932 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7933 {
7934         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7935         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7936
7937         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7938         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7939         if(myPawns == 2 && nMine == 3) // KPP
7940             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7941         if(myPawns == 1 && nMine == 2) // KP
7942             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7943         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7944             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7945         if(myPawns) return FALSE;
7946         if(pCnt[WhiteRook+side])
7947             return pCnt[BlackRook-side] ||
7948                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7949                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7950                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7951         if(pCnt[WhiteCannon+side]) {
7952             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7953             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7954         }
7955         if(pCnt[WhiteKnight+side])
7956             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7957         return FALSE;
7958 }
7959
7960 int
7961 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7962 {
7963         VariantClass v = gameInfo.variant;
7964
7965         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7966         if(v == VariantShatranj) return TRUE; // always winnable through baring
7967         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7968         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7969
7970         if(v == VariantXiangqi) {
7971                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7972
7973                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7974                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7975                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7976                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7977                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7978                 if(stale) // we have at least one last-rank P plus perhaps C
7979                     return majors // KPKX
7980                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7981                 else // KCA*E*
7982                     return pCnt[WhiteFerz+side] // KCAK
7983                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7984                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7985                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7986
7987         } else if(v == VariantKnightmate) {
7988                 if(nMine == 1) return FALSE;
7989                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
7990         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7991                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7992
7993                 if(nMine == 1) return FALSE; // bare King
7994                 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
7995                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7996                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7997                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7998                 if(pCnt[WhiteKnight+side])
7999                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8000                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8001                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8002                 if(nBishops)
8003                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8004                 if(pCnt[WhiteAlfil+side])
8005                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8006                 if(pCnt[WhiteWazir+side])
8007                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8008         }
8009
8010         return TRUE;
8011 }
8012
8013 int
8014 CompareWithRights (Board b1, Board b2)
8015 {
8016     int rights = 0;
8017     if(!CompareBoards(b1, b2)) return FALSE;
8018     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8019     /* compare castling rights */
8020     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8021            rights++; /* King lost rights, while rook still had them */
8022     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8023         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8024            rights++; /* but at least one rook lost them */
8025     }
8026     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8027            rights++;
8028     if( b1[CASTLING][5] != NoRights ) {
8029         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8030            rights++;
8031     }
8032     return rights == 0;
8033 }
8034
8035 int
8036 Adjudicate (ChessProgramState *cps)
8037 {       // [HGM] some adjudications useful with buggy engines
8038         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8039         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8040         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8041         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8042         int k, drop, count = 0; static int bare = 1;
8043         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8044         Boolean canAdjudicate = !appData.icsActive;
8045
8046         // most tests only when we understand the game, i.e. legality-checking on
8047             if( appData.testLegality )
8048             {   /* [HGM] Some more adjudications for obstinate engines */
8049                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
8050                 static int moveCount = 6;
8051                 ChessMove result;
8052                 char *reason = NULL;
8053
8054                 /* Count what is on board. */
8055                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8056
8057                 /* Some material-based adjudications that have to be made before stalemate test */
8058                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8059                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8060                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8061                      if(canAdjudicate && appData.checkMates) {
8062                          if(engineOpponent)
8063                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8064                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8065                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8066                          return 1;
8067                      }
8068                 }
8069
8070                 /* Bare King in Shatranj (loses) or Losers (wins) */
8071                 if( nrW == 1 || nrB == 1) {
8072                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8073                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8074                      if(canAdjudicate && appData.checkMates) {
8075                          if(engineOpponent)
8076                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8077                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8078                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8079                          return 1;
8080                      }
8081                   } else
8082                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8083                   {    /* bare King */
8084                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8085                         if(canAdjudicate && appData.checkMates) {
8086                             /* but only adjudicate if adjudication enabled */
8087                             if(engineOpponent)
8088                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8089                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8090                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8091                             return 1;
8092                         }
8093                   }
8094                 } else bare = 1;
8095
8096
8097             // don't wait for engine to announce game end if we can judge ourselves
8098             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8099               case MT_CHECK:
8100                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8101                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8102                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8103                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8104                             checkCnt++;
8105                         if(checkCnt >= 2) {
8106                             reason = "Xboard adjudication: 3rd check";
8107                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8108                             break;
8109                         }
8110                     }
8111                 }
8112               case MT_NONE:
8113               default:
8114                 break;
8115               case MT_STEALMATE:
8116               case MT_STALEMATE:
8117               case MT_STAINMATE:
8118                 reason = "Xboard adjudication: Stalemate";
8119                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8120                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8121                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8122                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8123                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8124                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8125                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8126                                                                         EP_CHECKMATE : EP_WINS);
8127                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8128                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8129                 }
8130                 break;
8131               case MT_CHECKMATE:
8132                 reason = "Xboard adjudication: Checkmate";
8133                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8134                 if(gameInfo.variant == VariantShogi) {
8135                     if(forwardMostMove > backwardMostMove
8136                        && moveList[forwardMostMove-1][1] == '@'
8137                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8138                         reason = "XBoard adjudication: pawn-drop mate";
8139                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8140                     }
8141                 }
8142                 break;
8143             }
8144
8145                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8146                     case EP_STALEMATE:
8147                         result = GameIsDrawn; break;
8148                     case EP_CHECKMATE:
8149                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8150                     case EP_WINS:
8151                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8152                     default:
8153                         result = EndOfFile;
8154                 }
8155                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8156                     if(engineOpponent)
8157                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8158                     GameEnds( result, reason, GE_XBOARD );
8159                     return 1;
8160                 }
8161
8162                 /* Next absolutely insufficient mating material. */
8163                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8164                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8165                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8166
8167                      /* always flag draws, for judging claims */
8168                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8169
8170                      if(canAdjudicate && appData.materialDraws) {
8171                          /* but only adjudicate them if adjudication enabled */
8172                          if(engineOpponent) {
8173                            SendToProgram("force\n", engineOpponent); // suppress reply
8174                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8175                          }
8176                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8177                          return 1;
8178                      }
8179                 }
8180
8181                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8182                 if(gameInfo.variant == VariantXiangqi ?
8183                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8184                  : nrW + nrB == 4 &&
8185                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8186                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8187                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8188                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8189                    ) ) {
8190                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8191                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8192                           if(engineOpponent) {
8193                             SendToProgram("force\n", engineOpponent); // suppress reply
8194                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8195                           }
8196                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8197                           return 1;
8198                      }
8199                 } else moveCount = 6;
8200             }
8201
8202         // Repetition draws and 50-move rule can be applied independently of legality testing
8203
8204                 /* Check for rep-draws */
8205                 count = 0;
8206                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8207                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8208                 for(k = forwardMostMove-2;
8209                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8210                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8211                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8212                     k-=2)
8213                 {   int rights=0;
8214                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8215                         /* compare castling rights */
8216                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8217                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8218                                 rights++; /* King lost rights, while rook still had them */
8219                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8220                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8221                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8222                                    rights++; /* but at least one rook lost them */
8223                         }
8224                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8225                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8226                                 rights++;
8227                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8228                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8229                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8230                                    rights++;
8231                         }
8232                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8233                             && appData.drawRepeats > 1) {
8234                              /* adjudicate after user-specified nr of repeats */
8235                              int result = GameIsDrawn;
8236                              char *details = "XBoard adjudication: repetition draw";
8237                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8238                                 // [HGM] xiangqi: check for forbidden perpetuals
8239                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8240                                 for(m=forwardMostMove; m>k; m-=2) {
8241                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8242                                         ourPerpetual = 0; // the current mover did not always check
8243                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8244                                         hisPerpetual = 0; // the opponent did not always check
8245                                 }
8246                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8247                                                                         ourPerpetual, hisPerpetual);
8248                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8249                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8250                                     details = "Xboard adjudication: perpetual checking";
8251                                 } else
8252                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8253                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8254                                 } else
8255                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8256                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8257                                         result = BlackWins;
8258                                         details = "Xboard adjudication: repetition";
8259                                     }
8260                                 } else // it must be XQ
8261                                 // Now check for perpetual chases
8262                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8263                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8264                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8265                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8266                                         static char resdet[MSG_SIZ];
8267                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8268                                         details = resdet;
8269                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8270                                     } else
8271                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8272                                         break; // Abort repetition-checking loop.
8273                                 }
8274                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8275                              }
8276                              if(engineOpponent) {
8277                                SendToProgram("force\n", engineOpponent); // suppress reply
8278                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8279                              }
8280                              GameEnds( result, details, GE_XBOARD );
8281                              return 1;
8282                         }
8283                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8284                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8285                     }
8286                 }
8287
8288                 /* Now we test for 50-move draws. Determine ply count */
8289                 count = forwardMostMove;
8290                 /* look for last irreversble move */
8291                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8292                     count--;
8293                 /* if we hit starting position, add initial plies */
8294                 if( count == backwardMostMove )
8295                     count -= initialRulePlies;
8296                 count = forwardMostMove - count;
8297                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8298                         // adjust reversible move counter for checks in Xiangqi
8299                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8300                         if(i < backwardMostMove) i = backwardMostMove;
8301                         while(i <= forwardMostMove) {
8302                                 lastCheck = inCheck; // check evasion does not count
8303                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8304                                 if(inCheck || lastCheck) count--; // check does not count
8305                                 i++;
8306                         }
8307                 }
8308                 if( count >= 100)
8309                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8310                          /* this is used to judge if draw claims are legal */
8311                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8312                          if(engineOpponent) {
8313                            SendToProgram("force\n", engineOpponent); // suppress reply
8314                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8315                          }
8316                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8317                          return 1;
8318                 }
8319
8320                 /* if draw offer is pending, treat it as a draw claim
8321                  * when draw condition present, to allow engines a way to
8322                  * claim draws before making their move to avoid a race
8323                  * condition occurring after their move
8324                  */
8325                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8326                          char *p = NULL;
8327                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8328                              p = "Draw claim: 50-move rule";
8329                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8330                              p = "Draw claim: 3-fold repetition";
8331                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8332                              p = "Draw claim: insufficient mating material";
8333                          if( p != NULL && canAdjudicate) {
8334                              if(engineOpponent) {
8335                                SendToProgram("force\n", engineOpponent); // suppress reply
8336                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8337                              }
8338                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8339                              return 1;
8340                          }
8341                 }
8342
8343                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8344                     if(engineOpponent) {
8345                       SendToProgram("force\n", engineOpponent); // suppress reply
8346                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8347                     }
8348                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8349                     return 1;
8350                 }
8351         return 0;
8352 }
8353
8354 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8355 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8356 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8357
8358 static int
8359 BitbaseProbe ()
8360 {
8361     int pieces[10], squares[10], cnt=0, r, f, res;
8362     static int loaded;
8363     static PPROBE_EGBB probeBB;
8364     if(!appData.testLegality) return 10;
8365     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8366     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8367     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8368     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8369         ChessSquare piece = boards[forwardMostMove][r][f];
8370         int black = (piece >= BlackPawn);
8371         int type = piece - black*BlackPawn;
8372         if(piece == EmptySquare) continue;
8373         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8374         if(type == WhiteKing) type = WhiteQueen + 1;
8375         type = egbbCode[type];
8376         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8377         pieces[cnt] = type + black*6;
8378         if(++cnt > 5) return 11;
8379     }
8380     pieces[cnt] = squares[cnt] = 0;
8381     // probe EGBB
8382     if(loaded == 2) return 13; // loading failed before
8383     if(loaded == 0) {
8384         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8385         HMODULE lib;
8386         PLOAD_EGBB loadBB;
8387         loaded = 2; // prepare for failure
8388         if(!path) return 13; // no egbb installed
8389         strncpy(buf, path + 8, MSG_SIZ);
8390         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8391         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8392         lib = LoadLibrary(buf);
8393         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8394         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8395         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8396         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8397         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8398         loaded = 1; // success!
8399     }
8400     res = probeBB(forwardMostMove & 1, pieces, squares);
8401     return res > 0 ? 1 : res < 0 ? -1 : 0;
8402 }
8403
8404 char *
8405 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8406 {   // [HGM] book: this routine intercepts moves to simulate book replies
8407     char *bookHit = NULL;
8408
8409     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8410         char buf[MSG_SIZ];
8411         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8412         SendToProgram(buf, cps);
8413     }
8414     //first determine if the incoming move brings opponent into his book
8415     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8416         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8417     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8418     if(bookHit != NULL && !cps->bookSuspend) {
8419         // make sure opponent is not going to reply after receiving move to book position
8420         SendToProgram("force\n", cps);
8421         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8422     }
8423     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8424     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8425     // now arrange restart after book miss
8426     if(bookHit) {
8427         // after a book hit we never send 'go', and the code after the call to this routine
8428         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8429         char buf[MSG_SIZ], *move = bookHit;
8430         if(cps->useSAN) {
8431             int fromX, fromY, toX, toY;
8432             char promoChar;
8433             ChessMove moveType;
8434             move = buf + 30;
8435             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8436                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8437                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8438                                     PosFlags(forwardMostMove),
8439                                     fromY, fromX, toY, toX, promoChar, move);
8440             } else {
8441                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8442                 bookHit = NULL;
8443             }
8444         }
8445         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8446         SendToProgram(buf, cps);
8447         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8448     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8449         SendToProgram("go\n", cps);
8450         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8451     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8452         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8453             SendToProgram("go\n", cps);
8454         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8455     }
8456     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8457 }
8458
8459 int
8460 LoadError (char *errmess, ChessProgramState *cps)
8461 {   // unloads engine and switches back to -ncp mode if it was first
8462     if(cps->initDone) return FALSE;
8463     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8464     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8465     cps->pr = NoProc;
8466     if(cps == &first) {
8467         appData.noChessProgram = TRUE;
8468         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8469         gameMode = BeginningOfGame; ModeHighlight();
8470         SetNCPMode();
8471     }
8472     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8473     DisplayMessage("", ""); // erase waiting message
8474     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8475     return TRUE;
8476 }
8477
8478 char *savedMessage;
8479 ChessProgramState *savedState;
8480 void
8481 DeferredBookMove (void)
8482 {
8483         if(savedState->lastPing != savedState->lastPong)
8484                     ScheduleDelayedEvent(DeferredBookMove, 10);
8485         else
8486         HandleMachineMove(savedMessage, savedState);
8487 }
8488
8489 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8490 static ChessProgramState *stalledEngine;
8491 static char stashedInputMove[MSG_SIZ];
8492
8493 void
8494 HandleMachineMove (char *message, ChessProgramState *cps)
8495 {
8496     static char firstLeg[20];
8497     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8498     char realname[MSG_SIZ];
8499     int fromX, fromY, toX, toY;
8500     ChessMove moveType;
8501     char promoChar, roar;
8502     char *p, *pv=buf1;
8503     int machineWhite, oldError;
8504     char *bookHit;
8505
8506     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8507         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8508         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8509             DisplayError(_("Invalid pairing from pairing engine"), 0);
8510             return;
8511         }
8512         pairingReceived = 1;
8513         NextMatchGame();
8514         return; // Skim the pairing messages here.
8515     }
8516
8517     oldError = cps->userError; cps->userError = 0;
8518
8519 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8520     /*
8521      * Kludge to ignore BEL characters
8522      */
8523     while (*message == '\007') message++;
8524
8525     /*
8526      * [HGM] engine debug message: ignore lines starting with '#' character
8527      */
8528     if(cps->debug && *message == '#') return;
8529
8530     /*
8531      * Look for book output
8532      */
8533     if (cps == &first && bookRequested) {
8534         if (message[0] == '\t' || message[0] == ' ') {
8535             /* Part of the book output is here; append it */
8536             strcat(bookOutput, message);
8537             strcat(bookOutput, "  \n");
8538             return;
8539         } else if (bookOutput[0] != NULLCHAR) {
8540             /* All of book output has arrived; display it */
8541             char *p = bookOutput;
8542             while (*p != NULLCHAR) {
8543                 if (*p == '\t') *p = ' ';
8544                 p++;
8545             }
8546             DisplayInformation(bookOutput);
8547             bookRequested = FALSE;
8548             /* Fall through to parse the current output */
8549         }
8550     }
8551
8552     /*
8553      * Look for machine move.
8554      */
8555     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8556         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8557     {
8558         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8559             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8560             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8561             stalledEngine = cps;
8562             if(appData.ponderNextMove) { // bring opponent out of ponder
8563                 if(gameMode == TwoMachinesPlay) {
8564                     if(cps->other->pause)
8565                         PauseEngine(cps->other);
8566                     else
8567                         SendToProgram("easy\n", cps->other);
8568                 }
8569             }
8570             StopClocks();
8571             return;
8572         }
8573
8574         /* This method is only useful on engines that support ping */
8575         if (cps->lastPing != cps->lastPong) {
8576           if (gameMode == BeginningOfGame) {
8577             /* Extra move from before last new; ignore */
8578             if (appData.debugMode) {
8579                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8580             }
8581           } else {
8582             if (appData.debugMode) {
8583                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8584                         cps->which, gameMode);
8585             }
8586
8587             SendToProgram("undo\n", cps);
8588           }
8589           return;
8590         }
8591
8592         switch (gameMode) {
8593           case BeginningOfGame:
8594             /* Extra move from before last reset; ignore */
8595             if (appData.debugMode) {
8596                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8597             }
8598             return;
8599
8600           case EndOfGame:
8601           case IcsIdle:
8602           default:
8603             /* Extra move after we tried to stop.  The mode test is
8604                not a reliable way of detecting this problem, but it's
8605                the best we can do on engines that don't support ping.
8606             */
8607             if (appData.debugMode) {
8608                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8609                         cps->which, gameMode);
8610             }
8611             SendToProgram("undo\n", cps);
8612             return;
8613
8614           case MachinePlaysWhite:
8615           case IcsPlayingWhite:
8616             machineWhite = TRUE;
8617             break;
8618
8619           case MachinePlaysBlack:
8620           case IcsPlayingBlack:
8621             machineWhite = FALSE;
8622             break;
8623
8624           case TwoMachinesPlay:
8625             machineWhite = (cps->twoMachinesColor[0] == 'w');
8626             break;
8627         }
8628         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8629             if (appData.debugMode) {
8630                 fprintf(debugFP,
8631                         "Ignoring move out of turn by %s, gameMode %d"
8632                         ", forwardMost %d\n",
8633                         cps->which, gameMode, forwardMostMove);
8634             }
8635             return;
8636         }
8637
8638         if(cps->alphaRank) AlphaRank(machineMove, 4);
8639
8640         // [HGM] lion: (some very limited) support for Alien protocol
8641         killX = killY = -1;
8642         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8643             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8644             return;
8645         } else if(firstLeg[0]) { // there was a previous leg;
8646             // only support case where same piece makes two step (and don't even test that!)
8647             char buf[20], *p = machineMove+1, *q = buf+1, f;
8648             safeStrCpy(buf, machineMove, 20);
8649             while(isdigit(*q)) q++; // find start of to-square
8650             safeStrCpy(machineMove, firstLeg, 20);
8651             while(isdigit(*p)) p++;
8652             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8653             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8654             firstLeg[0] = NULLCHAR;
8655         }
8656
8657         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8658                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8659             /* Machine move could not be parsed; ignore it. */
8660           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8661                     machineMove, _(cps->which));
8662             DisplayMoveError(buf1);
8663             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
8664                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
8665             if (gameMode == TwoMachinesPlay) {
8666               GameEnds(machineWhite ? BlackWins : WhiteWins,
8667                        buf1, GE_XBOARD);
8668             }
8669             return;
8670         }
8671
8672         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8673         /* So we have to redo legality test with true e.p. status here,  */
8674         /* to make sure an illegal e.p. capture does not slip through,   */
8675         /* to cause a forfeit on a justified illegal-move complaint      */
8676         /* of the opponent.                                              */
8677         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8678            ChessMove moveType;
8679            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8680                              fromY, fromX, toY, toX, promoChar);
8681             if(moveType == IllegalMove) {
8682               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8683                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8684                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8685                            buf1, GE_XBOARD);
8686                 return;
8687            } else if(!appData.fischerCastling)
8688            /* [HGM] Kludge to handle engines that send FRC-style castling
8689               when they shouldn't (like TSCP-Gothic) */
8690            switch(moveType) {
8691              case WhiteASideCastleFR:
8692              case BlackASideCastleFR:
8693                toX+=2;
8694                currentMoveString[2]++;
8695                break;
8696              case WhiteHSideCastleFR:
8697              case BlackHSideCastleFR:
8698                toX--;
8699                currentMoveString[2]--;
8700                break;
8701              default: ; // nothing to do, but suppresses warning of pedantic compilers
8702            }
8703         }
8704         hintRequested = FALSE;
8705         lastHint[0] = NULLCHAR;
8706         bookRequested = FALSE;
8707         /* Program may be pondering now */
8708         cps->maybeThinking = TRUE;
8709         if (cps->sendTime == 2) cps->sendTime = 1;
8710         if (cps->offeredDraw) cps->offeredDraw--;
8711
8712         /* [AS] Save move info*/
8713         pvInfoList[ forwardMostMove ].score = programStats.score;
8714         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8715         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8716
8717         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8718
8719         /* Test suites abort the 'game' after one move */
8720         if(*appData.finger) {
8721            static FILE *f;
8722            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8723            if(!f) f = fopen(appData.finger, "w");
8724            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8725            else { DisplayFatalError("Bad output file", errno, 0); return; }
8726            free(fen);
8727            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8728         }
8729
8730         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8731         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8732             int count = 0;
8733
8734             while( count < adjudicateLossPlies ) {
8735                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8736
8737                 if( count & 1 ) {
8738                     score = -score; /* Flip score for winning side */
8739                 }
8740
8741                 if( score > appData.adjudicateLossThreshold ) {
8742                     break;
8743                 }
8744
8745                 count++;
8746             }
8747
8748             if( count >= adjudicateLossPlies ) {
8749                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8750
8751                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8752                     "Xboard adjudication",
8753                     GE_XBOARD );
8754
8755                 return;
8756             }
8757         }
8758
8759         if(Adjudicate(cps)) {
8760             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8761             return; // [HGM] adjudicate: for all automatic game ends
8762         }
8763
8764 #if ZIPPY
8765         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8766             first.initDone) {
8767           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8768                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8769                 SendToICS("draw ");
8770                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8771           }
8772           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8773           ics_user_moved = 1;
8774           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8775                 char buf[3*MSG_SIZ];
8776
8777                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8778                         programStats.score / 100.,
8779                         programStats.depth,
8780                         programStats.time / 100.,
8781                         (unsigned int)programStats.nodes,
8782                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8783                         programStats.movelist);
8784                 SendToICS(buf);
8785           }
8786         }
8787 #endif
8788
8789         /* [AS] Clear stats for next move */
8790         ClearProgramStats();
8791         thinkOutput[0] = NULLCHAR;
8792         hiddenThinkOutputState = 0;
8793
8794         bookHit = NULL;
8795         if (gameMode == TwoMachinesPlay) {
8796             /* [HGM] relaying draw offers moved to after reception of move */
8797             /* and interpreting offer as claim if it brings draw condition */
8798             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8799                 SendToProgram("draw\n", cps->other);
8800             }
8801             if (cps->other->sendTime) {
8802                 SendTimeRemaining(cps->other,
8803                                   cps->other->twoMachinesColor[0] == 'w');
8804             }
8805             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8806             if (firstMove && !bookHit) {
8807                 firstMove = FALSE;
8808                 if (cps->other->useColors) {
8809                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8810                 }
8811                 SendToProgram("go\n", cps->other);
8812             }
8813             cps->other->maybeThinking = TRUE;
8814         }
8815
8816         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8817
8818         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8819
8820         if (!pausing && appData.ringBellAfterMoves) {
8821             if(!roar) RingBell();
8822         }
8823
8824         /*
8825          * Reenable menu items that were disabled while
8826          * machine was thinking
8827          */
8828         if (gameMode != TwoMachinesPlay)
8829             SetUserThinkingEnables();
8830
8831         // [HGM] book: after book hit opponent has received move and is now in force mode
8832         // force the book reply into it, and then fake that it outputted this move by jumping
8833         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8834         if(bookHit) {
8835                 static char bookMove[MSG_SIZ]; // a bit generous?
8836
8837                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8838                 strcat(bookMove, bookHit);
8839                 message = bookMove;
8840                 cps = cps->other;
8841                 programStats.nodes = programStats.depth = programStats.time =
8842                 programStats.score = programStats.got_only_move = 0;
8843                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8844
8845                 if(cps->lastPing != cps->lastPong) {
8846                     savedMessage = message; // args for deferred call
8847                     savedState = cps;
8848                     ScheduleDelayedEvent(DeferredBookMove, 10);
8849                     return;
8850                 }
8851                 goto FakeBookMove;
8852         }
8853
8854         return;
8855     }
8856
8857     /* Set special modes for chess engines.  Later something general
8858      *  could be added here; for now there is just one kludge feature,
8859      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8860      *  when "xboard" is given as an interactive command.
8861      */
8862     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8863         cps->useSigint = FALSE;
8864         cps->useSigterm = FALSE;
8865     }
8866     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8867       ParseFeatures(message+8, cps);
8868       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8869     }
8870
8871     if (!strncmp(message, "setup ", 6) && 
8872         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
8873           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
8874                                         ) { // [HGM] allow first engine to define opening position
8875       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
8876       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8877       *buf = NULLCHAR;
8878       if(sscanf(message, "setup (%s", buf) == 1) {
8879         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8880         ASSIGN(appData.pieceToCharTable, buf);
8881       }
8882       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
8883       if(dummy >= 3) {
8884         while(message[s] && message[s++] != ' ');
8885         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
8886            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
8887             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
8888             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
8889           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
8890           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
8891           startedFromSetupPosition = FALSE;
8892         }
8893       }
8894       if(startedFromSetupPosition) return;
8895       ParseFEN(boards[0], &dummy, message+s, FALSE);
8896       DrawPosition(TRUE, boards[0]);
8897       CopyBoard(initialPosition, boards[0]);
8898       startedFromSetupPosition = TRUE;
8899       return;
8900     }
8901     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
8902       ChessSquare piece = WhitePawn;
8903       char *p=buf2;
8904       if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
8905       piece += CharToPiece(*p) - WhitePawn;
8906       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
8907       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
8908       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
8909       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
8910       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
8911       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
8912                                                && gameInfo.variant != VariantGreat
8913                                                && gameInfo.variant != VariantFairy    ) return;
8914       if(piece < EmptySquare) {
8915         pieceDefs = TRUE;
8916         ASSIGN(pieceDesc[piece], buf1);
8917         if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
8918       }
8919       return;
8920     }
8921     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8922      * want this, I was asked to put it in, and obliged.
8923      */
8924     if (!strncmp(message, "setboard ", 9)) {
8925         Board initial_position;
8926
8927         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8928
8929         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
8930             DisplayError(_("Bad FEN received from engine"), 0);
8931             return ;
8932         } else {
8933            Reset(TRUE, FALSE);
8934            CopyBoard(boards[0], initial_position);
8935            initialRulePlies = FENrulePlies;
8936            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8937            else gameMode = MachinePlaysBlack;
8938            DrawPosition(FALSE, boards[currentMove]);
8939         }
8940         return;
8941     }
8942
8943     /*
8944      * Look for communication commands
8945      */
8946     if (!strncmp(message, "telluser ", 9)) {
8947         if(message[9] == '\\' && message[10] == '\\')
8948             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8949         PlayTellSound();
8950         DisplayNote(message + 9);
8951         return;
8952     }
8953     if (!strncmp(message, "tellusererror ", 14)) {
8954         cps->userError = 1;
8955         if(message[14] == '\\' && message[15] == '\\')
8956             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8957         PlayTellSound();
8958         DisplayError(message + 14, 0);
8959         return;
8960     }
8961     if (!strncmp(message, "tellopponent ", 13)) {
8962       if (appData.icsActive) {
8963         if (loggedOn) {
8964           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8965           SendToICS(buf1);
8966         }
8967       } else {
8968         DisplayNote(message + 13);
8969       }
8970       return;
8971     }
8972     if (!strncmp(message, "tellothers ", 11)) {
8973       if (appData.icsActive) {
8974         if (loggedOn) {
8975           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8976           SendToICS(buf1);
8977         }
8978       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
8979       return;
8980     }
8981     if (!strncmp(message, "tellall ", 8)) {
8982       if (appData.icsActive) {
8983         if (loggedOn) {
8984           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8985           SendToICS(buf1);
8986         }
8987       } else {
8988         DisplayNote(message + 8);
8989       }
8990       return;
8991     }
8992     if (strncmp(message, "warning", 7) == 0) {
8993         /* Undocumented feature, use tellusererror in new code */
8994         DisplayError(message, 0);
8995         return;
8996     }
8997     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8998         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8999         strcat(realname, " query");
9000         AskQuestion(realname, buf2, buf1, cps->pr);
9001         return;
9002     }
9003     /* Commands from the engine directly to ICS.  We don't allow these to be
9004      *  sent until we are logged on. Crafty kibitzes have been known to
9005      *  interfere with the login process.
9006      */
9007     if (loggedOn) {
9008         if (!strncmp(message, "tellics ", 8)) {
9009             SendToICS(message + 8);
9010             SendToICS("\n");
9011             return;
9012         }
9013         if (!strncmp(message, "tellicsnoalias ", 15)) {
9014             SendToICS(ics_prefix);
9015             SendToICS(message + 15);
9016             SendToICS("\n");
9017             return;
9018         }
9019         /* The following are for backward compatibility only */
9020         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9021             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9022             SendToICS(ics_prefix);
9023             SendToICS(message);
9024             SendToICS("\n");
9025             return;
9026         }
9027     }
9028     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9029         if(initPing == cps->lastPong) {
9030             if(gameInfo.variant == VariantUnknown) {
9031                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9032                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9033                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9034             }
9035             initPing = -1;
9036         }
9037         return;
9038     }
9039     if(!strncmp(message, "highlight ", 10)) {
9040         if(appData.testLegality && appData.markers) return;
9041         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9042         return;
9043     }
9044     if(!strncmp(message, "click ", 6)) {
9045         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9046         if(appData.testLegality || !appData.oneClick) return;
9047         sscanf(message+6, "%c%d%c", &f, &y, &c);
9048         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9049         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9050         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9051         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9052         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9053         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9054             LeftClick(Release, lastLeftX, lastLeftY);
9055         controlKey  = (c == ',');
9056         LeftClick(Press, x, y);
9057         LeftClick(Release, x, y);
9058         first.highlight = f;
9059         return;
9060     }
9061     /*
9062      * If the move is illegal, cancel it and redraw the board.
9063      * Also deal with other error cases.  Matching is rather loose
9064      * here to accommodate engines written before the spec.
9065      */
9066     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9067         strncmp(message, "Error", 5) == 0) {
9068         if (StrStr(message, "name") ||
9069             StrStr(message, "rating") || StrStr(message, "?") ||
9070             StrStr(message, "result") || StrStr(message, "board") ||
9071             StrStr(message, "bk") || StrStr(message, "computer") ||
9072             StrStr(message, "variant") || StrStr(message, "hint") ||
9073             StrStr(message, "random") || StrStr(message, "depth") ||
9074             StrStr(message, "accepted")) {
9075             return;
9076         }
9077         if (StrStr(message, "protover")) {
9078           /* Program is responding to input, so it's apparently done
9079              initializing, and this error message indicates it is
9080              protocol version 1.  So we don't need to wait any longer
9081              for it to initialize and send feature commands. */
9082           FeatureDone(cps, 1);
9083           cps->protocolVersion = 1;
9084           return;
9085         }
9086         cps->maybeThinking = FALSE;
9087
9088         if (StrStr(message, "draw")) {
9089             /* Program doesn't have "draw" command */
9090             cps->sendDrawOffers = 0;
9091             return;
9092         }
9093         if (cps->sendTime != 1 &&
9094             (StrStr(message, "time") || StrStr(message, "otim"))) {
9095           /* Program apparently doesn't have "time" or "otim" command */
9096           cps->sendTime = 0;
9097           return;
9098         }
9099         if (StrStr(message, "analyze")) {
9100             cps->analysisSupport = FALSE;
9101             cps->analyzing = FALSE;
9102 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9103             EditGameEvent(); // [HGM] try to preserve loaded game
9104             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9105             DisplayError(buf2, 0);
9106             return;
9107         }
9108         if (StrStr(message, "(no matching move)st")) {
9109           /* Special kludge for GNU Chess 4 only */
9110           cps->stKludge = TRUE;
9111           SendTimeControl(cps, movesPerSession, timeControl,
9112                           timeIncrement, appData.searchDepth,
9113                           searchTime);
9114           return;
9115         }
9116         if (StrStr(message, "(no matching move)sd")) {
9117           /* Special kludge for GNU Chess 4 only */
9118           cps->sdKludge = TRUE;
9119           SendTimeControl(cps, movesPerSession, timeControl,
9120                           timeIncrement, appData.searchDepth,
9121                           searchTime);
9122           return;
9123         }
9124         if (!StrStr(message, "llegal")) {
9125             return;
9126         }
9127         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9128             gameMode == IcsIdle) return;
9129         if (forwardMostMove <= backwardMostMove) return;
9130         if (pausing) PauseEvent();
9131       if(appData.forceIllegal) {
9132             // [HGM] illegal: machine refused move; force position after move into it
9133           SendToProgram("force\n", cps);
9134           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9135                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9136                 // when black is to move, while there might be nothing on a2 or black
9137                 // might already have the move. So send the board as if white has the move.
9138                 // But first we must change the stm of the engine, as it refused the last move
9139                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9140                 if(WhiteOnMove(forwardMostMove)) {
9141                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9142                     SendBoard(cps, forwardMostMove); // kludgeless board
9143                 } else {
9144                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9145                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9146                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9147                 }
9148           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9149             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9150                  gameMode == TwoMachinesPlay)
9151               SendToProgram("go\n", cps);
9152             return;
9153       } else
9154         if (gameMode == PlayFromGameFile) {
9155             /* Stop reading this game file */
9156             gameMode = EditGame;
9157             ModeHighlight();
9158         }
9159         /* [HGM] illegal-move claim should forfeit game when Xboard */
9160         /* only passes fully legal moves                            */
9161         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9162             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9163                                 "False illegal-move claim", GE_XBOARD );
9164             return; // do not take back move we tested as valid
9165         }
9166         currentMove = forwardMostMove-1;
9167         DisplayMove(currentMove-1); /* before DisplayMoveError */
9168         SwitchClocks(forwardMostMove-1); // [HGM] race
9169         DisplayBothClocks();
9170         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9171                 parseList[currentMove], _(cps->which));
9172         DisplayMoveError(buf1);
9173         DrawPosition(FALSE, boards[currentMove]);
9174
9175         SetUserThinkingEnables();
9176         return;
9177     }
9178     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9179         /* Program has a broken "time" command that
9180            outputs a string not ending in newline.
9181            Don't use it. */
9182         cps->sendTime = 0;
9183     }
9184     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9185         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9186             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9187     }
9188
9189     /*
9190      * If chess program startup fails, exit with an error message.
9191      * Attempts to recover here are futile. [HGM] Well, we try anyway
9192      */
9193     if ((StrStr(message, "unknown host") != NULL)
9194         || (StrStr(message, "No remote directory") != NULL)
9195         || (StrStr(message, "not found") != NULL)
9196         || (StrStr(message, "No such file") != NULL)
9197         || (StrStr(message, "can't alloc") != NULL)
9198         || (StrStr(message, "Permission denied") != NULL)) {
9199
9200         cps->maybeThinking = FALSE;
9201         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9202                 _(cps->which), cps->program, cps->host, message);
9203         RemoveInputSource(cps->isr);
9204         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9205             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9206             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9207         }
9208         return;
9209     }
9210
9211     /*
9212      * Look for hint output
9213      */
9214     if (sscanf(message, "Hint: %s", buf1) == 1) {
9215         if (cps == &first && hintRequested) {
9216             hintRequested = FALSE;
9217             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9218                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9219                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9220                                     PosFlags(forwardMostMove),
9221                                     fromY, fromX, toY, toX, promoChar, buf1);
9222                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9223                 DisplayInformation(buf2);
9224             } else {
9225                 /* Hint move could not be parsed!? */
9226               snprintf(buf2, sizeof(buf2),
9227                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9228                         buf1, _(cps->which));
9229                 DisplayError(buf2, 0);
9230             }
9231         } else {
9232           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9233         }
9234         return;
9235     }
9236
9237     /*
9238      * Ignore other messages if game is not in progress
9239      */
9240     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9241         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9242
9243     /*
9244      * look for win, lose, draw, or draw offer
9245      */
9246     if (strncmp(message, "1-0", 3) == 0) {
9247         char *p, *q, *r = "";
9248         p = strchr(message, '{');
9249         if (p) {
9250             q = strchr(p, '}');
9251             if (q) {
9252                 *q = NULLCHAR;
9253                 r = p + 1;
9254             }
9255         }
9256         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9257         return;
9258     } else if (strncmp(message, "0-1", 3) == 0) {
9259         char *p, *q, *r = "";
9260         p = strchr(message, '{');
9261         if (p) {
9262             q = strchr(p, '}');
9263             if (q) {
9264                 *q = NULLCHAR;
9265                 r = p + 1;
9266             }
9267         }
9268         /* Kludge for Arasan 4.1 bug */
9269         if (strcmp(r, "Black resigns") == 0) {
9270             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9271             return;
9272         }
9273         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9274         return;
9275     } else if (strncmp(message, "1/2", 3) == 0) {
9276         char *p, *q, *r = "";
9277         p = strchr(message, '{');
9278         if (p) {
9279             q = strchr(p, '}');
9280             if (q) {
9281                 *q = NULLCHAR;
9282                 r = p + 1;
9283             }
9284         }
9285
9286         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9287         return;
9288
9289     } else if (strncmp(message, "White resign", 12) == 0) {
9290         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9291         return;
9292     } else if (strncmp(message, "Black resign", 12) == 0) {
9293         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9294         return;
9295     } else if (strncmp(message, "White matches", 13) == 0 ||
9296                strncmp(message, "Black matches", 13) == 0   ) {
9297         /* [HGM] ignore GNUShogi noises */
9298         return;
9299     } else if (strncmp(message, "White", 5) == 0 &&
9300                message[5] != '(' &&
9301                StrStr(message, "Black") == NULL) {
9302         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9303         return;
9304     } else if (strncmp(message, "Black", 5) == 0 &&
9305                message[5] != '(') {
9306         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9307         return;
9308     } else if (strcmp(message, "resign") == 0 ||
9309                strcmp(message, "computer resigns") == 0) {
9310         switch (gameMode) {
9311           case MachinePlaysBlack:
9312           case IcsPlayingBlack:
9313             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9314             break;
9315           case MachinePlaysWhite:
9316           case IcsPlayingWhite:
9317             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9318             break;
9319           case TwoMachinesPlay:
9320             if (cps->twoMachinesColor[0] == 'w')
9321               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9322             else
9323               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9324             break;
9325           default:
9326             /* can't happen */
9327             break;
9328         }
9329         return;
9330     } else if (strncmp(message, "opponent mates", 14) == 0) {
9331         switch (gameMode) {
9332           case MachinePlaysBlack:
9333           case IcsPlayingBlack:
9334             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9335             break;
9336           case MachinePlaysWhite:
9337           case IcsPlayingWhite:
9338             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9339             break;
9340           case TwoMachinesPlay:
9341             if (cps->twoMachinesColor[0] == 'w')
9342               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9343             else
9344               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9345             break;
9346           default:
9347             /* can't happen */
9348             break;
9349         }
9350         return;
9351     } else if (strncmp(message, "computer mates", 14) == 0) {
9352         switch (gameMode) {
9353           case MachinePlaysBlack:
9354           case IcsPlayingBlack:
9355             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9356             break;
9357           case MachinePlaysWhite:
9358           case IcsPlayingWhite:
9359             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9360             break;
9361           case TwoMachinesPlay:
9362             if (cps->twoMachinesColor[0] == 'w')
9363               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9364             else
9365               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9366             break;
9367           default:
9368             /* can't happen */
9369             break;
9370         }
9371         return;
9372     } else if (strncmp(message, "checkmate", 9) == 0) {
9373         if (WhiteOnMove(forwardMostMove)) {
9374             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9375         } else {
9376             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9377         }
9378         return;
9379     } else if (strstr(message, "Draw") != NULL ||
9380                strstr(message, "game is a draw") != NULL) {
9381         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9382         return;
9383     } else if (strstr(message, "offer") != NULL &&
9384                strstr(message, "draw") != NULL) {
9385 #if ZIPPY
9386         if (appData.zippyPlay && first.initDone) {
9387             /* Relay offer to ICS */
9388             SendToICS(ics_prefix);
9389             SendToICS("draw\n");
9390         }
9391 #endif
9392         cps->offeredDraw = 2; /* valid until this engine moves twice */
9393         if (gameMode == TwoMachinesPlay) {
9394             if (cps->other->offeredDraw) {
9395                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9396             /* [HGM] in two-machine mode we delay relaying draw offer      */
9397             /* until after we also have move, to see if it is really claim */
9398             }
9399         } else if (gameMode == MachinePlaysWhite ||
9400                    gameMode == MachinePlaysBlack) {
9401           if (userOfferedDraw) {
9402             DisplayInformation(_("Machine accepts your draw offer"));
9403             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9404           } else {
9405             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9406           }
9407         }
9408     }
9409
9410
9411     /*
9412      * Look for thinking output
9413      */
9414     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9415           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9416                                 ) {
9417         int plylev, mvleft, mvtot, curscore, time;
9418         char mvname[MOVE_LEN];
9419         u64 nodes; // [DM]
9420         char plyext;
9421         int ignore = FALSE;
9422         int prefixHint = FALSE;
9423         mvname[0] = NULLCHAR;
9424
9425         switch (gameMode) {
9426           case MachinePlaysBlack:
9427           case IcsPlayingBlack:
9428             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9429             break;
9430           case MachinePlaysWhite:
9431           case IcsPlayingWhite:
9432             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9433             break;
9434           case AnalyzeMode:
9435           case AnalyzeFile:
9436             break;
9437           case IcsObserving: /* [DM] icsEngineAnalyze */
9438             if (!appData.icsEngineAnalyze) ignore = TRUE;
9439             break;
9440           case TwoMachinesPlay:
9441             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9442                 ignore = TRUE;
9443             }
9444             break;
9445           default:
9446             ignore = TRUE;
9447             break;
9448         }
9449
9450         if (!ignore) {
9451             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9452             buf1[0] = NULLCHAR;
9453             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9454                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9455
9456                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9457                     nodes += u64Const(0x100000000);
9458
9459                 if (plyext != ' ' && plyext != '\t') {
9460                     time *= 100;
9461                 }
9462
9463                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9464                 if( cps->scoreIsAbsolute &&
9465                     ( gameMode == MachinePlaysBlack ||
9466                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9467                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9468                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9469                      !WhiteOnMove(currentMove)
9470                     ) )
9471                 {
9472                     curscore = -curscore;
9473                 }
9474
9475                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9476
9477                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9478                         char buf[MSG_SIZ];
9479                         FILE *f;
9480                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9481                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9482                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9483                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9484                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9485                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9486                                 fclose(f);
9487                         }
9488                         else
9489                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9490                           DisplayError(_("failed writing PV"), 0);
9491                 }
9492
9493                 tempStats.depth = plylev;
9494                 tempStats.nodes = nodes;
9495                 tempStats.time = time;
9496                 tempStats.score = curscore;
9497                 tempStats.got_only_move = 0;
9498
9499                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9500                         int ticklen;
9501
9502                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9503                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9504                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9505                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9506                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9507                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9508                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9509                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9510                 }
9511
9512                 /* Buffer overflow protection */
9513                 if (pv[0] != NULLCHAR) {
9514                     if (strlen(pv) >= sizeof(tempStats.movelist)
9515                         && appData.debugMode) {
9516                         fprintf(debugFP,
9517                                 "PV is too long; using the first %u bytes.\n",
9518                                 (unsigned) sizeof(tempStats.movelist) - 1);
9519                     }
9520
9521                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9522                 } else {
9523                     sprintf(tempStats.movelist, " no PV\n");
9524                 }
9525
9526                 if (tempStats.seen_stat) {
9527                     tempStats.ok_to_send = 1;
9528                 }
9529
9530                 if (strchr(tempStats.movelist, '(') != NULL) {
9531                     tempStats.line_is_book = 1;
9532                     tempStats.nr_moves = 0;
9533                     tempStats.moves_left = 0;
9534                 } else {
9535                     tempStats.line_is_book = 0;
9536                 }
9537
9538                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9539                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9540
9541                 SendProgramStatsToFrontend( cps, &tempStats );
9542
9543                 /*
9544                     [AS] Protect the thinkOutput buffer from overflow... this
9545                     is only useful if buf1 hasn't overflowed first!
9546                 */
9547                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
9548                          plylev,
9549                          (gameMode == TwoMachinesPlay ?
9550                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9551                          ((double) curscore) / 100.0,
9552                          prefixHint ? lastHint : "",
9553                          prefixHint ? " " : "" );
9554
9555                 if( buf1[0] != NULLCHAR ) {
9556                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9557
9558                     if( strlen(pv) > max_len ) {
9559                         if( appData.debugMode) {
9560                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9561                         }
9562                         pv[max_len+1] = '\0';
9563                     }
9564
9565                     strcat( thinkOutput, pv);
9566                 }
9567
9568                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9569                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9570                     DisplayMove(currentMove - 1);
9571                 }
9572                 return;
9573
9574             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9575                 /* crafty (9.25+) says "(only move) <move>"
9576                  * if there is only 1 legal move
9577                  */
9578                 sscanf(p, "(only move) %s", buf1);
9579                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9580                 sprintf(programStats.movelist, "%s (only move)", buf1);
9581                 programStats.depth = 1;
9582                 programStats.nr_moves = 1;
9583                 programStats.moves_left = 1;
9584                 programStats.nodes = 1;
9585                 programStats.time = 1;
9586                 programStats.got_only_move = 1;
9587
9588                 /* Not really, but we also use this member to
9589                    mean "line isn't going to change" (Crafty
9590                    isn't searching, so stats won't change) */
9591                 programStats.line_is_book = 1;
9592
9593                 SendProgramStatsToFrontend( cps, &programStats );
9594
9595                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9596                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9597                     DisplayMove(currentMove - 1);
9598                 }
9599                 return;
9600             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9601                               &time, &nodes, &plylev, &mvleft,
9602                               &mvtot, mvname) >= 5) {
9603                 /* The stat01: line is from Crafty (9.29+) in response
9604                    to the "." command */
9605                 programStats.seen_stat = 1;
9606                 cps->maybeThinking = TRUE;
9607
9608                 if (programStats.got_only_move || !appData.periodicUpdates)
9609                   return;
9610
9611                 programStats.depth = plylev;
9612                 programStats.time = time;
9613                 programStats.nodes = nodes;
9614                 programStats.moves_left = mvleft;
9615                 programStats.nr_moves = mvtot;
9616                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9617                 programStats.ok_to_send = 1;
9618                 programStats.movelist[0] = '\0';
9619
9620                 SendProgramStatsToFrontend( cps, &programStats );
9621
9622                 return;
9623
9624             } else if (strncmp(message,"++",2) == 0) {
9625                 /* Crafty 9.29+ outputs this */
9626                 programStats.got_fail = 2;
9627                 return;
9628
9629             } else if (strncmp(message,"--",2) == 0) {
9630                 /* Crafty 9.29+ outputs this */
9631                 programStats.got_fail = 1;
9632                 return;
9633
9634             } else if (thinkOutput[0] != NULLCHAR &&
9635                        strncmp(message, "    ", 4) == 0) {
9636                 unsigned message_len;
9637
9638                 p = message;
9639                 while (*p && *p == ' ') p++;
9640
9641                 message_len = strlen( p );
9642
9643                 /* [AS] Avoid buffer overflow */
9644                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9645                     strcat(thinkOutput, " ");
9646                     strcat(thinkOutput, p);
9647                 }
9648
9649                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9650                     strcat(programStats.movelist, " ");
9651                     strcat(programStats.movelist, p);
9652                 }
9653
9654                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9655                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9656                     DisplayMove(currentMove - 1);
9657                 }
9658                 return;
9659             }
9660         }
9661         else {
9662             buf1[0] = NULLCHAR;
9663
9664             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9665                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9666             {
9667                 ChessProgramStats cpstats;
9668
9669                 if (plyext != ' ' && plyext != '\t') {
9670                     time *= 100;
9671                 }
9672
9673                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9674                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9675                     curscore = -curscore;
9676                 }
9677
9678                 cpstats.depth = plylev;
9679                 cpstats.nodes = nodes;
9680                 cpstats.time = time;
9681                 cpstats.score = curscore;
9682                 cpstats.got_only_move = 0;
9683                 cpstats.movelist[0] = '\0';
9684
9685                 if (buf1[0] != NULLCHAR) {
9686                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9687                 }
9688
9689                 cpstats.ok_to_send = 0;
9690                 cpstats.line_is_book = 0;
9691                 cpstats.nr_moves = 0;
9692                 cpstats.moves_left = 0;
9693
9694                 SendProgramStatsToFrontend( cps, &cpstats );
9695             }
9696         }
9697     }
9698 }
9699
9700
9701 /* Parse a game score from the character string "game", and
9702    record it as the history of the current game.  The game
9703    score is NOT assumed to start from the standard position.
9704    The display is not updated in any way.
9705    */
9706 void
9707 ParseGameHistory (char *game)
9708 {
9709     ChessMove moveType;
9710     int fromX, fromY, toX, toY, boardIndex;
9711     char promoChar;
9712     char *p, *q;
9713     char buf[MSG_SIZ];
9714
9715     if (appData.debugMode)
9716       fprintf(debugFP, "Parsing game history: %s\n", game);
9717
9718     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9719     gameInfo.site = StrSave(appData.icsHost);
9720     gameInfo.date = PGNDate();
9721     gameInfo.round = StrSave("-");
9722
9723     /* Parse out names of players */
9724     while (*game == ' ') game++;
9725     p = buf;
9726     while (*game != ' ') *p++ = *game++;
9727     *p = NULLCHAR;
9728     gameInfo.white = StrSave(buf);
9729     while (*game == ' ') game++;
9730     p = buf;
9731     while (*game != ' ' && *game != '\n') *p++ = *game++;
9732     *p = NULLCHAR;
9733     gameInfo.black = StrSave(buf);
9734
9735     /* Parse moves */
9736     boardIndex = blackPlaysFirst ? 1 : 0;
9737     yynewstr(game);
9738     for (;;) {
9739         yyboardindex = boardIndex;
9740         moveType = (ChessMove) Myylex();
9741         switch (moveType) {
9742           case IllegalMove:             /* maybe suicide chess, etc. */
9743   if (appData.debugMode) {
9744     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9745     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9746     setbuf(debugFP, NULL);
9747   }
9748           case WhitePromotion:
9749           case BlackPromotion:
9750           case WhiteNonPromotion:
9751           case BlackNonPromotion:
9752           case NormalMove:
9753           case FirstLeg:
9754           case WhiteCapturesEnPassant:
9755           case BlackCapturesEnPassant:
9756           case WhiteKingSideCastle:
9757           case WhiteQueenSideCastle:
9758           case BlackKingSideCastle:
9759           case BlackQueenSideCastle:
9760           case WhiteKingSideCastleWild:
9761           case WhiteQueenSideCastleWild:
9762           case BlackKingSideCastleWild:
9763           case BlackQueenSideCastleWild:
9764           /* PUSH Fabien */
9765           case WhiteHSideCastleFR:
9766           case WhiteASideCastleFR:
9767           case BlackHSideCastleFR:
9768           case BlackASideCastleFR:
9769           /* POP Fabien */
9770             fromX = currentMoveString[0] - AAA;
9771             fromY = currentMoveString[1] - ONE;
9772             toX = currentMoveString[2] - AAA;
9773             toY = currentMoveString[3] - ONE;
9774             promoChar = currentMoveString[4];
9775             break;
9776           case WhiteDrop:
9777           case BlackDrop:
9778             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9779             fromX = moveType == WhiteDrop ?
9780               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9781             (int) CharToPiece(ToLower(currentMoveString[0]));
9782             fromY = DROP_RANK;
9783             toX = currentMoveString[2] - AAA;
9784             toY = currentMoveString[3] - ONE;
9785             promoChar = NULLCHAR;
9786             break;
9787           case AmbiguousMove:
9788             /* bug? */
9789             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9790   if (appData.debugMode) {
9791     fprintf(debugFP, "Ambiguous 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             DisplayError(buf, 0);
9796             return;
9797           case ImpossibleMove:
9798             /* bug? */
9799             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9800   if (appData.debugMode) {
9801     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9802     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9803     setbuf(debugFP, NULL);
9804   }
9805             DisplayError(buf, 0);
9806             return;
9807           case EndOfFile:
9808             if (boardIndex < backwardMostMove) {
9809                 /* Oops, gap.  How did that happen? */
9810                 DisplayError(_("Gap in move list"), 0);
9811                 return;
9812             }
9813             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9814             if (boardIndex > forwardMostMove) {
9815                 forwardMostMove = boardIndex;
9816             }
9817             return;
9818           case ElapsedTime:
9819             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9820                 strcat(parseList[boardIndex-1], " ");
9821                 strcat(parseList[boardIndex-1], yy_text);
9822             }
9823             continue;
9824           case Comment:
9825           case PGNTag:
9826           case NAG:
9827           default:
9828             /* ignore */
9829             continue;
9830           case WhiteWins:
9831           case BlackWins:
9832           case GameIsDrawn:
9833           case GameUnfinished:
9834             if (gameMode == IcsExamining) {
9835                 if (boardIndex < backwardMostMove) {
9836                     /* Oops, gap.  How did that happen? */
9837                     return;
9838                 }
9839                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9840                 return;
9841             }
9842             gameInfo.result = moveType;
9843             p = strchr(yy_text, '{');
9844             if (p == NULL) p = strchr(yy_text, '(');
9845             if (p == NULL) {
9846                 p = yy_text;
9847                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9848             } else {
9849                 q = strchr(p, *p == '{' ? '}' : ')');
9850                 if (q != NULL) *q = NULLCHAR;
9851                 p++;
9852             }
9853             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9854             gameInfo.resultDetails = StrSave(p);
9855             continue;
9856         }
9857         if (boardIndex >= forwardMostMove &&
9858             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9859             backwardMostMove = blackPlaysFirst ? 1 : 0;
9860             return;
9861         }
9862         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9863                                  fromY, fromX, toY, toX, promoChar,
9864                                  parseList[boardIndex]);
9865         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9866         /* currentMoveString is set as a side-effect of yylex */
9867         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9868         strcat(moveList[boardIndex], "\n");
9869         boardIndex++;
9870         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9871         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9872           case MT_NONE:
9873           case MT_STALEMATE:
9874           default:
9875             break;
9876           case MT_CHECK:
9877             if(!IS_SHOGI(gameInfo.variant))
9878                 strcat(parseList[boardIndex - 1], "+");
9879             break;
9880           case MT_CHECKMATE:
9881           case MT_STAINMATE:
9882             strcat(parseList[boardIndex - 1], "#");
9883             break;
9884         }
9885     }
9886 }
9887
9888
9889 /* Apply a move to the given board  */
9890 void
9891 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9892 {
9893   ChessSquare captured = board[toY][toX], piece, king, killed; int p, rookX, oldEP = EP_NONE, berolina = 0;
9894   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
9895
9896     /* [HGM] compute & store e.p. status and castling rights for new position */
9897     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9898
9899       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9900       oldEP = (signed char)board[EP_STATUS];
9901       board[EP_STATUS] = EP_NONE;
9902       board[EP_FILE] = board[EP_RANK] = 100;
9903
9904   if (fromY == DROP_RANK) {
9905         /* must be first */
9906         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9907             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9908             return;
9909         }
9910         piece = board[toY][toX] = (ChessSquare) fromX;
9911   } else {
9912 //      ChessSquare victim;
9913       int i;
9914
9915       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
9916 //           victim = board[killY][killX],
9917            killed = board[killY][killX],
9918            board[killY][killX] = EmptySquare,
9919            board[EP_STATUS] = EP_CAPTURE;
9920
9921       if( board[toY][toX] != EmptySquare ) {
9922            board[EP_STATUS] = EP_CAPTURE;
9923            if( (fromX != toX || fromY != toY) && // not igui!
9924                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
9925                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
9926                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
9927            }
9928       }
9929
9930       piece = board[fromY][fromX];
9931       if( piece == WhiteLance || piece == BlackLance ) {
9932            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
9933                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
9934                else piece += WhiteLance - WhitePawn; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
9935            }
9936       }
9937       if( piece == WhitePawn ) {
9938            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9939                board[EP_STATUS] = EP_PAWN_MOVE;
9940            if( toY-fromY>=2) {
9941                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
9942                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9943                         gameInfo.variant != VariantBerolina || toX < fromX)
9944                       board[EP_STATUS] = toX | berolina;
9945                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9946                         gameInfo.variant != VariantBerolina || toX > fromX)
9947                       board[EP_STATUS] = toX;
9948            }
9949       } else
9950       if( piece == BlackPawn ) {
9951            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9952                board[EP_STATUS] = EP_PAWN_MOVE;
9953            if( toY-fromY<= -2) {
9954                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
9955                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9956                         gameInfo.variant != VariantBerolina || toX < fromX)
9957                       board[EP_STATUS] = toX | berolina;
9958                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9959                         gameInfo.variant != VariantBerolina || toX > fromX)
9960                       board[EP_STATUS] = toX;
9961            }
9962        }
9963
9964        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
9965        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
9966        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
9967        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
9968
9969        for(i=0; i<nrCastlingRights; i++) {
9970            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9971               board[CASTLING][i] == toX   && castlingRank[i] == toY
9972              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9973        }
9974
9975        if(gameInfo.variant == VariantSChess) { // update virginity
9976            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
9977            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
9978            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
9979            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
9980        }
9981
9982      if (fromX == toX && fromY == toY) return;
9983
9984      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9985      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9986      if(gameInfo.variant == VariantKnightmate)
9987          king += (int) WhiteUnicorn - (int) WhiteKing;
9988
9989     if(piece != WhiteKing && piece != BlackKing && pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
9990        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and captures own
9991         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
9992         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
9993         board[EP_STATUS] = EP_NONE; // capture was fake!
9994     } else
9995     /* Code added by Tord: */
9996     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9997     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9998         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9999       board[EP_STATUS] = EP_NONE; // capture was fake!
10000       board[fromY][fromX] = EmptySquare;
10001       board[toY][toX] = EmptySquare;
10002       if((toX > fromX) != (piece == WhiteRook)) {
10003         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10004       } else {
10005         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10006       }
10007     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10008                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10009       board[EP_STATUS] = EP_NONE;
10010       board[fromY][fromX] = EmptySquare;
10011       board[toY][toX] = EmptySquare;
10012       if((toX > fromX) != (piece == BlackRook)) {
10013         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10014       } else {
10015         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10016       }
10017     /* End of code added by Tord */
10018
10019     } else if (board[fromY][fromX] == king
10020         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10021         && toY == fromY && toX > fromX+1) {
10022         board[fromY][fromX] = EmptySquare;
10023         board[toY][toX] = king;
10024         for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
10025         board[toY][toX-1] = board[fromY][rookX];
10026         board[fromY][rookX] = EmptySquare;
10027     } else if (board[fromY][fromX] == king
10028         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10029                && toY == fromY && toX < fromX-1) {
10030         board[fromY][fromX] = EmptySquare;
10031         board[toY][toX] = king;
10032         for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
10033         board[toY][toX+1] = board[fromY][rookX];
10034         board[fromY][rookX] = EmptySquare;
10035     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10036                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10037                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10038                ) {
10039         /* white pawn promotion */
10040         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10041         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10042             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10043         board[fromY][fromX] = EmptySquare;
10044     } else if ((fromY >= BOARD_HEIGHT>>1)
10045                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10046                && (toX != fromX)
10047                && gameInfo.variant != VariantXiangqi
10048                && gameInfo.variant != VariantBerolina
10049                && (board[fromY][fromX] == WhitePawn)
10050                && (board[toY][toX] == EmptySquare)) {
10051         board[fromY][fromX] = EmptySquare;
10052         board[toY][toX] = WhitePawn;
10053         if(toY == board[EP_RANK] - 128 + 1)
10054             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10055         else
10056             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10057     } else if ((fromY == BOARD_HEIGHT-4)
10058                && (toX == fromX)
10059                && gameInfo.variant == VariantBerolina
10060                && (board[fromY][fromX] == WhitePawn)
10061                && (board[toY][toX] == EmptySquare)) {
10062         board[fromY][fromX] = EmptySquare;
10063         board[toY][toX] = WhitePawn;
10064         if(oldEP & EP_BEROLIN_A) {
10065                 captured = board[fromY][fromX-1];
10066                 board[fromY][fromX-1] = EmptySquare;
10067         }else{  captured = board[fromY][fromX+1];
10068                 board[fromY][fromX+1] = EmptySquare;
10069         }
10070     } else if (board[fromY][fromX] == king
10071         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10072                && toY == fromY && toX > fromX+1) {
10073         board[fromY][fromX] = EmptySquare;
10074         board[toY][toX] = king;
10075         for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
10076         board[toY][toX-1] = board[fromY][rookX];
10077         board[fromY][rookX] = EmptySquare;
10078     } else if (board[fromY][fromX] == king
10079         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10080                && toY == fromY && toX < fromX-1) {
10081         board[fromY][fromX] = EmptySquare;
10082         board[toY][toX] = king;
10083         for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
10084         board[toY][toX+1] = board[fromY][rookX];
10085         board[fromY][rookX] = EmptySquare;
10086     } else if (fromY == 7 && fromX == 3
10087                && board[fromY][fromX] == BlackKing
10088                && toY == 7 && toX == 5) {
10089         board[fromY][fromX] = EmptySquare;
10090         board[toY][toX] = BlackKing;
10091         board[fromY][7] = EmptySquare;
10092         board[toY][4] = BlackRook;
10093     } else if (fromY == 7 && fromX == 3
10094                && board[fromY][fromX] == BlackKing
10095                && toY == 7 && toX == 1) {
10096         board[fromY][fromX] = EmptySquare;
10097         board[toY][toX] = BlackKing;
10098         board[fromY][0] = EmptySquare;
10099         board[toY][2] = BlackRook;
10100     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10101                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10102                && toY < promoRank && promoChar
10103                ) {
10104         /* black pawn promotion */
10105         board[toY][toX] = CharToPiece(ToLower(promoChar));
10106         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
10107             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
10108         board[fromY][fromX] = EmptySquare;
10109     } else if ((fromY < BOARD_HEIGHT>>1)
10110                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10111                && (toX != fromX)
10112                && gameInfo.variant != VariantXiangqi
10113                && gameInfo.variant != VariantBerolina
10114                && (board[fromY][fromX] == BlackPawn)
10115                && (board[toY][toX] == EmptySquare)) {
10116         board[fromY][fromX] = EmptySquare;
10117         board[toY][toX] = BlackPawn;
10118         if(toY == board[EP_RANK] - 128 - 1)
10119             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10120         else
10121             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10122     } else if ((fromY == 3)
10123                && (toX == fromX)
10124                && gameInfo.variant == VariantBerolina
10125                && (board[fromY][fromX] == BlackPawn)
10126                && (board[toY][toX] == EmptySquare)) {
10127         board[fromY][fromX] = EmptySquare;
10128         board[toY][toX] = BlackPawn;
10129         if(oldEP & EP_BEROLIN_A) {
10130                 captured = board[fromY][fromX-1];
10131                 board[fromY][fromX-1] = EmptySquare;
10132         }else{  captured = board[fromY][fromX+1];
10133                 board[fromY][fromX+1] = EmptySquare;
10134         }
10135     } else {
10136         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10137         board[fromY][fromX] = EmptySquare;
10138         board[toY][toX] = piece;
10139     }
10140   }
10141
10142     if (gameInfo.holdingsWidth != 0) {
10143
10144       /* !!A lot more code needs to be written to support holdings  */
10145       /* [HGM] OK, so I have written it. Holdings are stored in the */
10146       /* penultimate board files, so they are automaticlly stored   */
10147       /* in the game history.                                       */
10148       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10149                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10150         /* Delete from holdings, by decreasing count */
10151         /* and erasing image if necessary            */
10152         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10153         if(p < (int) BlackPawn) { /* white drop */
10154              p -= (int)WhitePawn;
10155                  p = PieceToNumber((ChessSquare)p);
10156              if(p >= gameInfo.holdingsSize) p = 0;
10157              if(--board[p][BOARD_WIDTH-2] <= 0)
10158                   board[p][BOARD_WIDTH-1] = EmptySquare;
10159              if((int)board[p][BOARD_WIDTH-2] < 0)
10160                         board[p][BOARD_WIDTH-2] = 0;
10161         } else {                  /* black drop */
10162              p -= (int)BlackPawn;
10163                  p = PieceToNumber((ChessSquare)p);
10164              if(p >= gameInfo.holdingsSize) p = 0;
10165              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10166                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10167              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10168                         board[BOARD_HEIGHT-1-p][1] = 0;
10169         }
10170       }
10171       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10172           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10173         /* [HGM] holdings: Add to holdings, if holdings exist */
10174         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10175                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10176                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10177         }
10178         p = (int) captured;
10179         if (p >= (int) BlackPawn) {
10180           p -= (int)BlackPawn;
10181           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10182                   /* Restore shogi-promoted piece to its original  first */
10183                   captured = (ChessSquare) (DEMOTED captured);
10184                   p = DEMOTED p;
10185           }
10186           p = PieceToNumber((ChessSquare)p);
10187           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10188           board[p][BOARD_WIDTH-2]++;
10189           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10190         } else {
10191           p -= (int)WhitePawn;
10192           if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
10193                   captured = (ChessSquare) (DEMOTED captured);
10194                   p = DEMOTED p;
10195           }
10196           p = PieceToNumber((ChessSquare)p);
10197           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10198           board[BOARD_HEIGHT-1-p][1]++;
10199           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10200         }
10201       }
10202     } else if (gameInfo.variant == VariantAtomic) {
10203       if (captured != EmptySquare) {
10204         int y, x;
10205         for (y = toY-1; y <= toY+1; y++) {
10206           for (x = toX-1; x <= toX+1; x++) {
10207             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10208                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10209               board[y][x] = EmptySquare;
10210             }
10211           }
10212         }
10213         board[toY][toX] = EmptySquare;
10214       }
10215     }
10216
10217     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10218         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10219     } else
10220     if(promoChar == '+') {
10221         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10222         board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
10223         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10224           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10225     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10226         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10227         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
10228            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
10229         board[toY][toX] = newPiece;
10230     }
10231     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10232                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10233         // [HGM] superchess: take promotion piece out of holdings
10234         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10235         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10236             if(!--board[k][BOARD_WIDTH-2])
10237                 board[k][BOARD_WIDTH-1] = EmptySquare;
10238         } else {
10239             if(!--board[BOARD_HEIGHT-1-k][1])
10240                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10241         }
10242     }
10243 }
10244
10245 /* Updates forwardMostMove */
10246 void
10247 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10248 {
10249     int x = toX, y = toY;
10250     char *s = parseList[forwardMostMove];
10251     ChessSquare p = boards[forwardMostMove][toY][toX];
10252 //    forwardMostMove++; // [HGM] bare: moved downstream
10253
10254     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10255     (void) CoordsToAlgebraic(boards[forwardMostMove],
10256                              PosFlags(forwardMostMove),
10257                              fromY, fromX, y, x, promoChar,
10258                              s);
10259     if(killX >= 0 && killY >= 0)
10260         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10261
10262     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10263         int timeLeft; static int lastLoadFlag=0; int king, piece;
10264         piece = boards[forwardMostMove][fromY][fromX];
10265         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10266         if(gameInfo.variant == VariantKnightmate)
10267             king += (int) WhiteUnicorn - (int) WhiteKing;
10268         if(forwardMostMove == 0) {
10269             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10270                 fprintf(serverMoves, "%s;", UserName());
10271             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10272                 fprintf(serverMoves, "%s;", second.tidy);
10273             fprintf(serverMoves, "%s;", first.tidy);
10274             if(gameMode == MachinePlaysWhite)
10275                 fprintf(serverMoves, "%s;", UserName());
10276             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10277                 fprintf(serverMoves, "%s;", second.tidy);
10278         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10279         lastLoadFlag = loadFlag;
10280         // print base move
10281         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10282         // print castling suffix
10283         if( toY == fromY && piece == king ) {
10284             if(toX-fromX > 1)
10285                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10286             if(fromX-toX >1)
10287                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10288         }
10289         // e.p. suffix
10290         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10291              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10292              boards[forwardMostMove][toY][toX] == EmptySquare
10293              && fromX != toX && fromY != toY)
10294                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10295         // promotion suffix
10296         if(promoChar != NULLCHAR) {
10297             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10298                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10299                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10300             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10301         }
10302         if(!loadFlag) {
10303                 char buf[MOVE_LEN*2], *p; int len;
10304             fprintf(serverMoves, "/%d/%d",
10305                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10306             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10307             else                      timeLeft = blackTimeRemaining/1000;
10308             fprintf(serverMoves, "/%d", timeLeft);
10309                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10310                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10311                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10312                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10313             fprintf(serverMoves, "/%s", buf);
10314         }
10315         fflush(serverMoves);
10316     }
10317
10318     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10319         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10320       return;
10321     }
10322     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10323     if (commentList[forwardMostMove+1] != NULL) {
10324         free(commentList[forwardMostMove+1]);
10325         commentList[forwardMostMove+1] = NULL;
10326     }
10327     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10328     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10329     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10330     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10331     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10332     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10333     adjustedClock = FALSE;
10334     gameInfo.result = GameUnfinished;
10335     if (gameInfo.resultDetails != NULL) {
10336         free(gameInfo.resultDetails);
10337         gameInfo.resultDetails = NULL;
10338     }
10339     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10340                               moveList[forwardMostMove - 1]);
10341     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10342       case MT_NONE:
10343       case MT_STALEMATE:
10344       default:
10345         break;
10346       case MT_CHECK:
10347         if(!IS_SHOGI(gameInfo.variant))
10348             strcat(parseList[forwardMostMove - 1], "+");
10349         break;
10350       case MT_CHECKMATE:
10351       case MT_STAINMATE:
10352         strcat(parseList[forwardMostMove - 1], "#");
10353         break;
10354     }
10355 }
10356
10357 /* Updates currentMove if not pausing */
10358 void
10359 ShowMove (int fromX, int fromY, int toX, int toY)
10360 {
10361     int instant = (gameMode == PlayFromGameFile) ?
10362         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10363     if(appData.noGUI) return;
10364     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10365         if (!instant) {
10366             if (forwardMostMove == currentMove + 1) {
10367                 AnimateMove(boards[forwardMostMove - 1],
10368                             fromX, fromY, toX, toY);
10369             }
10370         }
10371         currentMove = forwardMostMove;
10372     }
10373
10374     killX = killY = -1; // [HGM] lion: used up
10375
10376     if (instant) return;
10377
10378     DisplayMove(currentMove - 1);
10379     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10380             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10381                 SetHighlights(fromX, fromY, toX, toY);
10382             }
10383     }
10384     DrawPosition(FALSE, boards[currentMove]);
10385     DisplayBothClocks();
10386     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10387 }
10388
10389 void
10390 SendEgtPath (ChessProgramState *cps)
10391 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10392         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10393
10394         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10395
10396         while(*p) {
10397             char c, *q = name+1, *r, *s;
10398
10399             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10400             while(*p && *p != ',') *q++ = *p++;
10401             *q++ = ':'; *q = 0;
10402             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10403                 strcmp(name, ",nalimov:") == 0 ) {
10404                 // take nalimov path from the menu-changeable option first, if it is defined
10405               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10406                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10407             } else
10408             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10409                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10410                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10411                 s = r = StrStr(s, ":") + 1; // beginning of path info
10412                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10413                 c = *r; *r = 0;             // temporarily null-terminate path info
10414                     *--q = 0;               // strip of trailig ':' from name
10415                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10416                 *r = c;
10417                 SendToProgram(buf,cps);     // send egtbpath command for this format
10418             }
10419             if(*p == ',') p++; // read away comma to position for next format name
10420         }
10421 }
10422
10423 static int
10424 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10425 {
10426       int width = 8, height = 8, holdings = 0;             // most common sizes
10427       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10428       // correct the deviations default for each variant
10429       if( v == VariantXiangqi ) width = 9,  height = 10;
10430       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10431       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10432       if( v == VariantCapablanca || v == VariantCapaRandom ||
10433           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10434                                 width = 10;
10435       if( v == VariantCourier ) width = 12;
10436       if( v == VariantSuper )                            holdings = 8;
10437       if( v == VariantGreat )   width = 10,              holdings = 8;
10438       if( v == VariantSChess )                           holdings = 7;
10439       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10440       if( v == VariantChuChess) width = 10, height = 10;
10441       if( v == VariantChu )     width = 12, height = 12;
10442       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10443              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10444              holdingsSize >= 0 && holdingsSize != holdings;
10445 }
10446
10447 char variantError[MSG_SIZ];
10448
10449 char *
10450 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10451 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10452       char *p, *variant = VariantName(v);
10453       static char b[MSG_SIZ];
10454       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10455            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10456                                                holdingsSize, variant); // cook up sized variant name
10457            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10458            if(StrStr(list, b) == NULL) {
10459                // specific sized variant not known, check if general sizing allowed
10460                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10461                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10462                             boardWidth, boardHeight, holdingsSize, engine);
10463                    return NULL;
10464                }
10465                /* [HGM] here we really should compare with the maximum supported board size */
10466            }
10467       } else snprintf(b, MSG_SIZ,"%s", variant);
10468       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10469       p = StrStr(list, b);
10470       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10471       if(p == NULL) {
10472           // occurs not at all in list, or only as sub-string
10473           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10474           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10475               int l = strlen(variantError);
10476               char *q;
10477               while(p != list && p[-1] != ',') p--;
10478               q = strchr(p, ',');
10479               if(q) *q = NULLCHAR;
10480               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10481               if(q) *q= ',';
10482           }
10483           return NULL;
10484       }
10485       return b;
10486 }
10487
10488 void
10489 InitChessProgram (ChessProgramState *cps, int setup)
10490 /* setup needed to setup FRC opening position */
10491 {
10492     char buf[MSG_SIZ], *b;
10493     if (appData.noChessProgram) return;
10494     hintRequested = FALSE;
10495     bookRequested = FALSE;
10496
10497     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10498     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10499     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10500     if(cps->memSize) { /* [HGM] memory */
10501       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10502         SendToProgram(buf, cps);
10503     }
10504     SendEgtPath(cps); /* [HGM] EGT */
10505     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10506       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10507         SendToProgram(buf, cps);
10508     }
10509
10510     setboardSpoiledMachineBlack = FALSE;
10511     SendToProgram(cps->initString, cps);
10512     if (gameInfo.variant != VariantNormal &&
10513         gameInfo.variant != VariantLoadable
10514         /* [HGM] also send variant if board size non-standard */
10515         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10516
10517       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10518                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10519       if (b == NULL) {
10520         VariantClass v;
10521         char c, *q = cps->variants, *p = strchr(q, ',');
10522         if(p) *p = NULLCHAR;
10523         v = StringToVariant(q);
10524         DisplayError(variantError, 0);
10525         if(v != VariantUnknown && cps == &first) {
10526             int w, h, s;
10527             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10528                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10529             ASSIGN(appData.variant, q);
10530             Reset(TRUE, FALSE);
10531         }
10532         if(p) *p = ',';
10533         return;
10534       }
10535
10536       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10537       SendToProgram(buf, cps);
10538     }
10539     currentlyInitializedVariant = gameInfo.variant;
10540
10541     /* [HGM] send opening position in FRC to first engine */
10542     if(setup) {
10543           SendToProgram("force\n", cps);
10544           SendBoard(cps, 0);
10545           /* engine is now in force mode! Set flag to wake it up after first move. */
10546           setboardSpoiledMachineBlack = 1;
10547     }
10548
10549     if (cps->sendICS) {
10550       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10551       SendToProgram(buf, cps);
10552     }
10553     cps->maybeThinking = FALSE;
10554     cps->offeredDraw = 0;
10555     if (!appData.icsActive) {
10556         SendTimeControl(cps, movesPerSession, timeControl,
10557                         timeIncrement, appData.searchDepth,
10558                         searchTime);
10559     }
10560     if (appData.showThinking
10561         // [HGM] thinking: four options require thinking output to be sent
10562         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10563                                 ) {
10564         SendToProgram("post\n", cps);
10565     }
10566     SendToProgram("hard\n", cps);
10567     if (!appData.ponderNextMove) {
10568         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10569            it without being sure what state we are in first.  "hard"
10570            is not a toggle, so that one is OK.
10571          */
10572         SendToProgram("easy\n", cps);
10573     }
10574     if (cps->usePing) {
10575       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10576       SendToProgram(buf, cps);
10577     }
10578     cps->initDone = TRUE;
10579     ClearEngineOutputPane(cps == &second);
10580 }
10581
10582
10583 void
10584 ResendOptions (ChessProgramState *cps)
10585 { // send the stored value of the options
10586   int i;
10587   char buf[MSG_SIZ];
10588   Option *opt = cps->option;
10589   for(i=0; i<cps->nrOptions; i++, opt++) {
10590       switch(opt->type) {
10591         case Spin:
10592         case Slider:
10593         case CheckBox:
10594             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10595           break;
10596         case ComboBox:
10597           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10598           break;
10599         default:
10600             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10601           break;
10602         case Button:
10603         case SaveButton:
10604           continue;
10605       }
10606       SendToProgram(buf, cps);
10607   }
10608 }
10609
10610 void
10611 StartChessProgram (ChessProgramState *cps)
10612 {
10613     char buf[MSG_SIZ];
10614     int err;
10615
10616     if (appData.noChessProgram) return;
10617     cps->initDone = FALSE;
10618
10619     if (strcmp(cps->host, "localhost") == 0) {
10620         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10621     } else if (*appData.remoteShell == NULLCHAR) {
10622         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10623     } else {
10624         if (*appData.remoteUser == NULLCHAR) {
10625           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10626                     cps->program);
10627         } else {
10628           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10629                     cps->host, appData.remoteUser, cps->program);
10630         }
10631         err = StartChildProcess(buf, "", &cps->pr);
10632     }
10633
10634     if (err != 0) {
10635       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10636         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10637         if(cps != &first) return;
10638         appData.noChessProgram = TRUE;
10639         ThawUI();
10640         SetNCPMode();
10641 //      DisplayFatalError(buf, err, 1);
10642 //      cps->pr = NoProc;
10643 //      cps->isr = NULL;
10644         return;
10645     }
10646
10647     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10648     if (cps->protocolVersion > 1) {
10649       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10650       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10651         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10652         cps->comboCnt = 0;  //                and values of combo boxes
10653       }
10654       SendToProgram(buf, cps);
10655       if(cps->reload) ResendOptions(cps);
10656     } else {
10657       SendToProgram("xboard\n", cps);
10658     }
10659 }
10660
10661 void
10662 TwoMachinesEventIfReady P((void))
10663 {
10664   static int curMess = 0;
10665   if (first.lastPing != first.lastPong) {
10666     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10667     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10668     return;
10669   }
10670   if (second.lastPing != second.lastPong) {
10671     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10672     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10673     return;
10674   }
10675   DisplayMessage("", ""); curMess = 0;
10676   TwoMachinesEvent();
10677 }
10678
10679 char *
10680 MakeName (char *template)
10681 {
10682     time_t clock;
10683     struct tm *tm;
10684     static char buf[MSG_SIZ];
10685     char *p = buf;
10686     int i;
10687
10688     clock = time((time_t *)NULL);
10689     tm = localtime(&clock);
10690
10691     while(*p++ = *template++) if(p[-1] == '%') {
10692         switch(*template++) {
10693           case 0:   *p = 0; return buf;
10694           case 'Y': i = tm->tm_year+1900; break;
10695           case 'y': i = tm->tm_year-100; break;
10696           case 'M': i = tm->tm_mon+1; break;
10697           case 'd': i = tm->tm_mday; break;
10698           case 'h': i = tm->tm_hour; break;
10699           case 'm': i = tm->tm_min; break;
10700           case 's': i = tm->tm_sec; break;
10701           default:  i = 0;
10702         }
10703         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10704     }
10705     return buf;
10706 }
10707
10708 int
10709 CountPlayers (char *p)
10710 {
10711     int n = 0;
10712     while(p = strchr(p, '\n')) p++, n++; // count participants
10713     return n;
10714 }
10715
10716 FILE *
10717 WriteTourneyFile (char *results, FILE *f)
10718 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10719     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10720     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10721         // create a file with tournament description
10722         fprintf(f, "-participants {%s}\n", appData.participants);
10723         fprintf(f, "-seedBase %d\n", appData.seedBase);
10724         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10725         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10726         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10727         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10728         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10729         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10730         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10731         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10732         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10733         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10734         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10735         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10736         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10737         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10738         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10739         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10740         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10741         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10742         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10743         fprintf(f, "-smpCores %d\n", appData.smpCores);
10744         if(searchTime > 0)
10745                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10746         else {
10747                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10748                 fprintf(f, "-tc %s\n", appData.timeControl);
10749                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10750         }
10751         fprintf(f, "-results \"%s\"\n", results);
10752     }
10753     return f;
10754 }
10755
10756 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10757
10758 void
10759 Substitute (char *participants, int expunge)
10760 {
10761     int i, changed, changes=0, nPlayers=0;
10762     char *p, *q, *r, buf[MSG_SIZ];
10763     if(participants == NULL) return;
10764     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10765     r = p = participants; q = appData.participants;
10766     while(*p && *p == *q) {
10767         if(*p == '\n') r = p+1, nPlayers++;
10768         p++; q++;
10769     }
10770     if(*p) { // difference
10771         while(*p && *p++ != '\n');
10772         while(*q && *q++ != '\n');
10773       changed = nPlayers;
10774         changes = 1 + (strcmp(p, q) != 0);
10775     }
10776     if(changes == 1) { // a single engine mnemonic was changed
10777         q = r; while(*q) nPlayers += (*q++ == '\n');
10778         p = buf; while(*r && (*p = *r++) != '\n') p++;
10779         *p = NULLCHAR;
10780         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10781         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10782         if(mnemonic[i]) { // The substitute is valid
10783             FILE *f;
10784             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10785                 flock(fileno(f), LOCK_EX);
10786                 ParseArgsFromFile(f);
10787                 fseek(f, 0, SEEK_SET);
10788                 FREE(appData.participants); appData.participants = participants;
10789                 if(expunge) { // erase results of replaced engine
10790                     int len = strlen(appData.results), w, b, dummy;
10791                     for(i=0; i<len; i++) {
10792                         Pairing(i, nPlayers, &w, &b, &dummy);
10793                         if((w == changed || b == changed) && appData.results[i] == '*') {
10794                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10795                             fclose(f);
10796                             return;
10797                         }
10798                     }
10799                     for(i=0; i<len; i++) {
10800                         Pairing(i, nPlayers, &w, &b, &dummy);
10801                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10802                     }
10803                 }
10804                 WriteTourneyFile(appData.results, f);
10805                 fclose(f); // release lock
10806                 return;
10807             }
10808         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10809     }
10810     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
10811     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
10812     free(participants);
10813     return;
10814 }
10815
10816 int
10817 CheckPlayers (char *participants)
10818 {
10819         int i;
10820         char buf[MSG_SIZ], *p;
10821         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10822         while(p = strchr(participants, '\n')) {
10823             *p = NULLCHAR;
10824             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
10825             if(!mnemonic[i]) {
10826                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
10827                 *p = '\n';
10828                 DisplayError(buf, 0);
10829                 return 1;
10830             }
10831             *p = '\n';
10832             participants = p + 1;
10833         }
10834         return 0;
10835 }
10836
10837 int
10838 CreateTourney (char *name)
10839 {
10840         FILE *f;
10841         if(matchMode && strcmp(name, appData.tourneyFile)) {
10842              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10843         }
10844         if(name[0] == NULLCHAR) {
10845             if(appData.participants[0])
10846                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10847             return 0;
10848         }
10849         f = fopen(name, "r");
10850         if(f) { // file exists
10851             ASSIGN(appData.tourneyFile, name);
10852             ParseArgsFromFile(f); // parse it
10853         } else {
10854             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10855             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10856                 DisplayError(_("Not enough participants"), 0);
10857                 return 0;
10858             }
10859             if(CheckPlayers(appData.participants)) return 0;
10860             ASSIGN(appData.tourneyFile, name);
10861             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10862             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10863         }
10864         fclose(f);
10865         appData.noChessProgram = FALSE;
10866         appData.clockMode = TRUE;
10867         SetGNUMode();
10868         return 1;
10869 }
10870
10871 int
10872 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10873 {
10874     char buf[MSG_SIZ], *p, *q;
10875     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10876     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10877     skip = !all && group[0]; // if group requested, we start in skip mode
10878     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10879         p = names; q = buf; header = 0;
10880         while(*p && *p != '\n') *q++ = *p++;
10881         *q = 0;
10882         if(*p == '\n') p++;
10883         if(buf[0] == '#') {
10884             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10885             depth++; // we must be entering a new group
10886             if(all) continue; // suppress printing group headers when complete list requested
10887             header = 1;
10888             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10889         }
10890         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10891         if(engineList[i]) free(engineList[i]);
10892         engineList[i] = strdup(buf);
10893         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10894         if(engineMnemonic[i]) free(engineMnemonic[i]);
10895         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10896             strcat(buf, " (");
10897             sscanf(q + 8, "%s", buf + strlen(buf));
10898             strcat(buf, ")");
10899         }
10900         engineMnemonic[i] = strdup(buf);
10901         i++;
10902     }
10903     engineList[i] = engineMnemonic[i] = NULL;
10904     return i;
10905 }
10906
10907 // following implemented as macro to avoid type limitations
10908 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10909
10910 void
10911 SwapEngines (int n)
10912 {   // swap settings for first engine and other engine (so far only some selected options)
10913     int h;
10914     char *p;
10915     if(n == 0) return;
10916     SWAP(directory, p)
10917     SWAP(chessProgram, p)
10918     SWAP(isUCI, h)
10919     SWAP(hasOwnBookUCI, h)
10920     SWAP(protocolVersion, h)
10921     SWAP(reuse, h)
10922     SWAP(scoreIsAbsolute, h)
10923     SWAP(timeOdds, h)
10924     SWAP(logo, p)
10925     SWAP(pgnName, p)
10926     SWAP(pvSAN, h)
10927     SWAP(engOptions, p)
10928     SWAP(engInitString, p)
10929     SWAP(computerString, p)
10930     SWAP(features, p)
10931     SWAP(fenOverride, p)
10932     SWAP(NPS, h)
10933     SWAP(accumulateTC, h)
10934     SWAP(drawDepth, h)
10935     SWAP(host, p)
10936     SWAP(pseudo, h)
10937 }
10938
10939 int
10940 GetEngineLine (char *s, int n)
10941 {
10942     int i;
10943     char buf[MSG_SIZ];
10944     extern char *icsNames;
10945     if(!s || !*s) return 0;
10946     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
10947     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
10948     if(!mnemonic[i]) return 0;
10949     if(n == 11) return 1; // just testing if there was a match
10950     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
10951     if(n == 1) SwapEngines(n);
10952     ParseArgsFromString(buf);
10953     if(n == 1) SwapEngines(n);
10954     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
10955         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
10956         ParseArgsFromString(buf);
10957     }
10958     return 1;
10959 }
10960
10961 int
10962 SetPlayer (int player, char *p)
10963 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10964     int i;
10965     char buf[MSG_SIZ], *engineName;
10966     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10967     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10968     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10969     if(mnemonic[i]) {
10970         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10971         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10972         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10973         ParseArgsFromString(buf);
10974     } else { // no engine with this nickname is installed!
10975         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
10976         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
10977         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10978         ModeHighlight();
10979         DisplayError(buf, 0);
10980         return 0;
10981     }
10982     free(engineName);
10983     return i;
10984 }
10985
10986 char *recentEngines;
10987
10988 void
10989 RecentEngineEvent (int nr)
10990 {
10991     int n;
10992 //    SwapEngines(1); // bump first to second
10993 //    ReplaceEngine(&second, 1); // and load it there
10994     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10995     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10996     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10997         ReplaceEngine(&first, 0);
10998         FloatToFront(&appData.recentEngineList, command[n]);
10999     }
11000 }
11001
11002 int
11003 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11004 {   // determine players from game number
11005     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11006
11007     if(appData.tourneyType == 0) {
11008         roundsPerCycle = (nPlayers - 1) | 1;
11009         pairingsPerRound = nPlayers / 2;
11010     } else if(appData.tourneyType > 0) {
11011         roundsPerCycle = nPlayers - appData.tourneyType;
11012         pairingsPerRound = appData.tourneyType;
11013     }
11014     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11015     gamesPerCycle = gamesPerRound * roundsPerCycle;
11016     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11017     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11018     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11019     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11020     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11021     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11022
11023     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11024     if(appData.roundSync) *syncInterval = gamesPerRound;
11025
11026     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11027
11028     if(appData.tourneyType == 0) {
11029         if(curPairing == (nPlayers-1)/2 ) {
11030             *whitePlayer = curRound;
11031             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11032         } else {
11033             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11034             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11035             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11036             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11037         }
11038     } else if(appData.tourneyType > 1) {
11039         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11040         *whitePlayer = curRound + appData.tourneyType;
11041     } else if(appData.tourneyType > 0) {
11042         *whitePlayer = curPairing;
11043         *blackPlayer = curRound + appData.tourneyType;
11044     }
11045
11046     // take care of white/black alternation per round.
11047     // For cycles and games this is already taken care of by default, derived from matchGame!
11048     return curRound & 1;
11049 }
11050
11051 int
11052 NextTourneyGame (int nr, int *swapColors)
11053 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11054     char *p, *q;
11055     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11056     FILE *tf;
11057     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11058     tf = fopen(appData.tourneyFile, "r");
11059     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11060     ParseArgsFromFile(tf); fclose(tf);
11061     InitTimeControls(); // TC might be altered from tourney file
11062
11063     nPlayers = CountPlayers(appData.participants); // count participants
11064     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11065     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11066
11067     if(syncInterval) {
11068         p = q = appData.results;
11069         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11070         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11071             DisplayMessage(_("Waiting for other game(s)"),"");
11072             waitingForGame = TRUE;
11073             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11074             return 0;
11075         }
11076         waitingForGame = FALSE;
11077     }
11078
11079     if(appData.tourneyType < 0) {
11080         if(nr>=0 && !pairingReceived) {
11081             char buf[1<<16];
11082             if(pairing.pr == NoProc) {
11083                 if(!appData.pairingEngine[0]) {
11084                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11085                     return 0;
11086                 }
11087                 StartChessProgram(&pairing); // starts the pairing engine
11088             }
11089             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11090             SendToProgram(buf, &pairing);
11091             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11092             SendToProgram(buf, &pairing);
11093             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11094         }
11095         pairingReceived = 0;                              // ... so we continue here
11096         *swapColors = 0;
11097         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11098         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11099         matchGame = 1; roundNr = nr / syncInterval + 1;
11100     }
11101
11102     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11103
11104     // redefine engines, engine dir, etc.
11105     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11106     if(first.pr == NoProc) {
11107       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11108       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11109     }
11110     if(second.pr == NoProc) {
11111       SwapEngines(1);
11112       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11113       SwapEngines(1);         // and make that valid for second engine by swapping
11114       InitEngine(&second, 1);
11115     }
11116     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11117     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11118     return OK;
11119 }
11120
11121 void
11122 NextMatchGame ()
11123 {   // performs game initialization that does not invoke engines, and then tries to start the game
11124     int res, firstWhite, swapColors = 0;
11125     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11126     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
11127         char buf[MSG_SIZ];
11128         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11129         if(strcmp(buf, currentDebugFile)) { // name has changed
11130             FILE *f = fopen(buf, "w");
11131             if(f) { // if opening the new file failed, just keep using the old one
11132                 ASSIGN(currentDebugFile, buf);
11133                 fclose(debugFP);
11134                 debugFP = f;
11135             }
11136             if(appData.serverFileName) {
11137                 if(serverFP) fclose(serverFP);
11138                 serverFP = fopen(appData.serverFileName, "w");
11139                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11140                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11141             }
11142         }
11143     }
11144     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11145     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11146     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11147     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11148     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11149     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11150     Reset(FALSE, first.pr != NoProc);
11151     res = LoadGameOrPosition(matchGame); // setup game
11152     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11153     if(!res) return; // abort when bad game/pos file
11154     TwoMachinesEvent();
11155 }
11156
11157 void
11158 UserAdjudicationEvent (int result)
11159 {
11160     ChessMove gameResult = GameIsDrawn;
11161
11162     if( result > 0 ) {
11163         gameResult = WhiteWins;
11164     }
11165     else if( result < 0 ) {
11166         gameResult = BlackWins;
11167     }
11168
11169     if( gameMode == TwoMachinesPlay ) {
11170         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11171     }
11172 }
11173
11174
11175 // [HGM] save: calculate checksum of game to make games easily identifiable
11176 int
11177 StringCheckSum (char *s)
11178 {
11179         int i = 0;
11180         if(s==NULL) return 0;
11181         while(*s) i = i*259 + *s++;
11182         return i;
11183 }
11184
11185 int
11186 GameCheckSum ()
11187 {
11188         int i, sum=0;
11189         for(i=backwardMostMove; i<forwardMostMove; i++) {
11190                 sum += pvInfoList[i].depth;
11191                 sum += StringCheckSum(parseList[i]);
11192                 sum += StringCheckSum(commentList[i]);
11193                 sum *= 261;
11194         }
11195         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11196         return sum + StringCheckSum(commentList[i]);
11197 } // end of save patch
11198
11199 void
11200 GameEnds (ChessMove result, char *resultDetails, int whosays)
11201 {
11202     GameMode nextGameMode;
11203     int isIcsGame;
11204     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11205
11206     if(endingGame) return; /* [HGM] crash: forbid recursion */
11207     endingGame = 1;
11208     if(twoBoards) { // [HGM] dual: switch back to one board
11209         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11210         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11211     }
11212     if (appData.debugMode) {
11213       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11214               result, resultDetails ? resultDetails : "(null)", whosays);
11215     }
11216
11217     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11218
11219     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11220
11221     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11222         /* If we are playing on ICS, the server decides when the
11223            game is over, but the engine can offer to draw, claim
11224            a draw, or resign.
11225          */
11226 #if ZIPPY
11227         if (appData.zippyPlay && first.initDone) {
11228             if (result == GameIsDrawn) {
11229                 /* In case draw still needs to be claimed */
11230                 SendToICS(ics_prefix);
11231                 SendToICS("draw\n");
11232             } else if (StrCaseStr(resultDetails, "resign")) {
11233                 SendToICS(ics_prefix);
11234                 SendToICS("resign\n");
11235             }
11236         }
11237 #endif
11238         endingGame = 0; /* [HGM] crash */
11239         return;
11240     }
11241
11242     /* If we're loading the game from a file, stop */
11243     if (whosays == GE_FILE) {
11244       (void) StopLoadGameTimer();
11245       gameFileFP = NULL;
11246     }
11247
11248     /* Cancel draw offers */
11249     first.offeredDraw = second.offeredDraw = 0;
11250
11251     /* If this is an ICS game, only ICS can really say it's done;
11252        if not, anyone can. */
11253     isIcsGame = (gameMode == IcsPlayingWhite ||
11254                  gameMode == IcsPlayingBlack ||
11255                  gameMode == IcsObserving    ||
11256                  gameMode == IcsExamining);
11257
11258     if (!isIcsGame || whosays == GE_ICS) {
11259         /* OK -- not an ICS game, or ICS said it was done */
11260         StopClocks();
11261         if (!isIcsGame && !appData.noChessProgram)
11262           SetUserThinkingEnables();
11263
11264         /* [HGM] if a machine claims the game end we verify this claim */
11265         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11266             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11267                 char claimer;
11268                 ChessMove trueResult = (ChessMove) -1;
11269
11270                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11271                                             first.twoMachinesColor[0] :
11272                                             second.twoMachinesColor[0] ;
11273
11274                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11275                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11276                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11277                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11278                 } else
11279                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11280                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11281                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11282                 } else
11283                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11284                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11285                 }
11286
11287                 // now verify win claims, but not in drop games, as we don't understand those yet
11288                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11289                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11290                     (result == WhiteWins && claimer == 'w' ||
11291                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11292                       if (appData.debugMode) {
11293                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11294                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11295                       }
11296                       if(result != trueResult) {
11297                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11298                               result = claimer == 'w' ? BlackWins : WhiteWins;
11299                               resultDetails = buf;
11300                       }
11301                 } else
11302                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11303                     && (forwardMostMove <= backwardMostMove ||
11304                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11305                         (claimer=='b')==(forwardMostMove&1))
11306                                                                                   ) {
11307                       /* [HGM] verify: draws that were not flagged are false claims */
11308                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11309                       result = claimer == 'w' ? BlackWins : WhiteWins;
11310                       resultDetails = buf;
11311                 }
11312                 /* (Claiming a loss is accepted no questions asked!) */
11313             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11314                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11315                 result = GameUnfinished;
11316                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11317             }
11318             /* [HGM] bare: don't allow bare King to win */
11319             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11320                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11321                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11322                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11323                && result != GameIsDrawn)
11324             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11325                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11326                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11327                         if(p >= 0 && p <= (int)WhiteKing) k++;
11328                 }
11329                 if (appData.debugMode) {
11330                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11331                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11332                 }
11333                 if(k <= 1) {
11334                         result = GameIsDrawn;
11335                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11336                         resultDetails = buf;
11337                 }
11338             }
11339         }
11340
11341
11342         if(serverMoves != NULL && !loadFlag) { char c = '=';
11343             if(result==WhiteWins) c = '+';
11344             if(result==BlackWins) c = '-';
11345             if(resultDetails != NULL)
11346                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11347         }
11348         if (resultDetails != NULL) {
11349             gameInfo.result = result;
11350             gameInfo.resultDetails = StrSave(resultDetails);
11351
11352             /* display last move only if game was not loaded from file */
11353             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11354                 DisplayMove(currentMove - 1);
11355
11356             if (forwardMostMove != 0) {
11357                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11358                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11359                                                                 ) {
11360                     if (*appData.saveGameFile != NULLCHAR) {
11361                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11362                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11363                         else
11364                         SaveGameToFile(appData.saveGameFile, TRUE);
11365                     } else if (appData.autoSaveGames) {
11366                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11367                     }
11368                     if (*appData.savePositionFile != NULLCHAR) {
11369                         SavePositionToFile(appData.savePositionFile);
11370                     }
11371                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11372                 }
11373             }
11374
11375             /* Tell program how game ended in case it is learning */
11376             /* [HGM] Moved this to after saving the PGN, just in case */
11377             /* engine died and we got here through time loss. In that */
11378             /* case we will get a fatal error writing the pipe, which */
11379             /* would otherwise lose us the PGN.                       */
11380             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11381             /* output during GameEnds should never be fatal anymore   */
11382             if (gameMode == MachinePlaysWhite ||
11383                 gameMode == MachinePlaysBlack ||
11384                 gameMode == TwoMachinesPlay ||
11385                 gameMode == IcsPlayingWhite ||
11386                 gameMode == IcsPlayingBlack ||
11387                 gameMode == BeginningOfGame) {
11388                 char buf[MSG_SIZ];
11389                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11390                         resultDetails);
11391                 if (first.pr != NoProc) {
11392                     SendToProgram(buf, &first);
11393                 }
11394                 if (second.pr != NoProc &&
11395                     gameMode == TwoMachinesPlay) {
11396                     SendToProgram(buf, &second);
11397                 }
11398             }
11399         }
11400
11401         if (appData.icsActive) {
11402             if (appData.quietPlay &&
11403                 (gameMode == IcsPlayingWhite ||
11404                  gameMode == IcsPlayingBlack)) {
11405                 SendToICS(ics_prefix);
11406                 SendToICS("set shout 1\n");
11407             }
11408             nextGameMode = IcsIdle;
11409             ics_user_moved = FALSE;
11410             /* clean up premove.  It's ugly when the game has ended and the
11411              * premove highlights are still on the board.
11412              */
11413             if (gotPremove) {
11414               gotPremove = FALSE;
11415               ClearPremoveHighlights();
11416               DrawPosition(FALSE, boards[currentMove]);
11417             }
11418             if (whosays == GE_ICS) {
11419                 switch (result) {
11420                 case WhiteWins:
11421                     if (gameMode == IcsPlayingWhite)
11422                         PlayIcsWinSound();
11423                     else if(gameMode == IcsPlayingBlack)
11424                         PlayIcsLossSound();
11425                     break;
11426                 case BlackWins:
11427                     if (gameMode == IcsPlayingBlack)
11428                         PlayIcsWinSound();
11429                     else if(gameMode == IcsPlayingWhite)
11430                         PlayIcsLossSound();
11431                     break;
11432                 case GameIsDrawn:
11433                     PlayIcsDrawSound();
11434                     break;
11435                 default:
11436                     PlayIcsUnfinishedSound();
11437                 }
11438             }
11439             if(appData.quitNext) { ExitEvent(0); return; }
11440         } else if (gameMode == EditGame ||
11441                    gameMode == PlayFromGameFile ||
11442                    gameMode == AnalyzeMode ||
11443                    gameMode == AnalyzeFile) {
11444             nextGameMode = gameMode;
11445         } else {
11446             nextGameMode = EndOfGame;
11447         }
11448         pausing = FALSE;
11449         ModeHighlight();
11450     } else {
11451         nextGameMode = gameMode;
11452     }
11453
11454     if (appData.noChessProgram) {
11455         gameMode = nextGameMode;
11456         ModeHighlight();
11457         endingGame = 0; /* [HGM] crash */
11458         return;
11459     }
11460
11461     if (first.reuse) {
11462         /* Put first chess program into idle state */
11463         if (first.pr != NoProc &&
11464             (gameMode == MachinePlaysWhite ||
11465              gameMode == MachinePlaysBlack ||
11466              gameMode == TwoMachinesPlay ||
11467              gameMode == IcsPlayingWhite ||
11468              gameMode == IcsPlayingBlack ||
11469              gameMode == BeginningOfGame)) {
11470             SendToProgram("force\n", &first);
11471             if (first.usePing) {
11472               char buf[MSG_SIZ];
11473               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11474               SendToProgram(buf, &first);
11475             }
11476         }
11477     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11478         /* Kill off first chess program */
11479         if (first.isr != NULL)
11480           RemoveInputSource(first.isr);
11481         first.isr = NULL;
11482
11483         if (first.pr != NoProc) {
11484             ExitAnalyzeMode();
11485             DoSleep( appData.delayBeforeQuit );
11486             SendToProgram("quit\n", &first);
11487             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11488             first.reload = TRUE;
11489         }
11490         first.pr = NoProc;
11491     }
11492     if (second.reuse) {
11493         /* Put second chess program into idle state */
11494         if (second.pr != NoProc &&
11495             gameMode == TwoMachinesPlay) {
11496             SendToProgram("force\n", &second);
11497             if (second.usePing) {
11498               char buf[MSG_SIZ];
11499               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11500               SendToProgram(buf, &second);
11501             }
11502         }
11503     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11504         /* Kill off second chess program */
11505         if (second.isr != NULL)
11506           RemoveInputSource(second.isr);
11507         second.isr = NULL;
11508
11509         if (second.pr != NoProc) {
11510             DoSleep( appData.delayBeforeQuit );
11511             SendToProgram("quit\n", &second);
11512             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11513             second.reload = TRUE;
11514         }
11515         second.pr = NoProc;
11516     }
11517
11518     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11519         char resChar = '=';
11520         switch (result) {
11521         case WhiteWins:
11522           resChar = '+';
11523           if (first.twoMachinesColor[0] == 'w') {
11524             first.matchWins++;
11525           } else {
11526             second.matchWins++;
11527           }
11528           break;
11529         case BlackWins:
11530           resChar = '-';
11531           if (first.twoMachinesColor[0] == 'b') {
11532             first.matchWins++;
11533           } else {
11534             second.matchWins++;
11535           }
11536           break;
11537         case GameUnfinished:
11538           resChar = ' ';
11539         default:
11540           break;
11541         }
11542
11543         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11544         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11545             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11546             ReserveGame(nextGame, resChar); // sets nextGame
11547             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11548             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11549         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11550
11551         if (nextGame <= appData.matchGames && !abortMatch) {
11552             gameMode = nextGameMode;
11553             matchGame = nextGame; // this will be overruled in tourney mode!
11554             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11555             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11556             endingGame = 0; /* [HGM] crash */
11557             return;
11558         } else {
11559             gameMode = nextGameMode;
11560             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11561                      first.tidy, second.tidy,
11562                      first.matchWins, second.matchWins,
11563                      appData.matchGames - (first.matchWins + second.matchWins));
11564             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11565             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11566             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11567             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11568                 first.twoMachinesColor = "black\n";
11569                 second.twoMachinesColor = "white\n";
11570             } else {
11571                 first.twoMachinesColor = "white\n";
11572                 second.twoMachinesColor = "black\n";
11573             }
11574         }
11575     }
11576     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11577         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11578       ExitAnalyzeMode();
11579     gameMode = nextGameMode;
11580     ModeHighlight();
11581     endingGame = 0;  /* [HGM] crash */
11582     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11583         if(matchMode == TRUE) { // match through command line: exit with or without popup
11584             if(ranking) {
11585                 ToNrEvent(forwardMostMove);
11586                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11587                 else ExitEvent(0);
11588             } else DisplayFatalError(buf, 0, 0);
11589         } else { // match through menu; just stop, with or without popup
11590             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11591             ModeHighlight();
11592             if(ranking){
11593                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11594             } else DisplayNote(buf);
11595       }
11596       if(ranking) free(ranking);
11597     }
11598 }
11599
11600 /* Assumes program was just initialized (initString sent).
11601    Leaves program in force mode. */
11602 void
11603 FeedMovesToProgram (ChessProgramState *cps, int upto)
11604 {
11605     int i;
11606
11607     if (appData.debugMode)
11608       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11609               startedFromSetupPosition ? "position and " : "",
11610               backwardMostMove, upto, cps->which);
11611     if(currentlyInitializedVariant != gameInfo.variant) {
11612       char buf[MSG_SIZ];
11613         // [HGM] variantswitch: make engine aware of new variant
11614         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11615                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11616                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11617         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11618         SendToProgram(buf, cps);
11619         currentlyInitializedVariant = gameInfo.variant;
11620     }
11621     SendToProgram("force\n", cps);
11622     if (startedFromSetupPosition) {
11623         SendBoard(cps, backwardMostMove);
11624     if (appData.debugMode) {
11625         fprintf(debugFP, "feedMoves\n");
11626     }
11627     }
11628     for (i = backwardMostMove; i < upto; i++) {
11629         SendMoveToProgram(i, cps);
11630     }
11631 }
11632
11633
11634 int
11635 ResurrectChessProgram ()
11636 {
11637      /* The chess program may have exited.
11638         If so, restart it and feed it all the moves made so far. */
11639     static int doInit = 0;
11640
11641     if (appData.noChessProgram) return 1;
11642
11643     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11644         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11645         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11646         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11647     } else {
11648         if (first.pr != NoProc) return 1;
11649         StartChessProgram(&first);
11650     }
11651     InitChessProgram(&first, FALSE);
11652     FeedMovesToProgram(&first, currentMove);
11653
11654     if (!first.sendTime) {
11655         /* can't tell gnuchess what its clock should read,
11656            so we bow to its notion. */
11657         ResetClocks();
11658         timeRemaining[0][currentMove] = whiteTimeRemaining;
11659         timeRemaining[1][currentMove] = blackTimeRemaining;
11660     }
11661
11662     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11663                 appData.icsEngineAnalyze) && first.analysisSupport) {
11664       SendToProgram("analyze\n", &first);
11665       first.analyzing = TRUE;
11666     }
11667     return 1;
11668 }
11669
11670 /*
11671  * Button procedures
11672  */
11673 void
11674 Reset (int redraw, int init)
11675 {
11676     int i;
11677
11678     if (appData.debugMode) {
11679         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11680                 redraw, init, gameMode);
11681     }
11682     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11683     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11684     CleanupTail(); // [HGM] vari: delete any stored variations
11685     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11686     pausing = pauseExamInvalid = FALSE;
11687     startedFromSetupPosition = blackPlaysFirst = FALSE;
11688     firstMove = TRUE;
11689     whiteFlag = blackFlag = FALSE;
11690     userOfferedDraw = FALSE;
11691     hintRequested = bookRequested = FALSE;
11692     first.maybeThinking = FALSE;
11693     second.maybeThinking = FALSE;
11694     first.bookSuspend = FALSE; // [HGM] book
11695     second.bookSuspend = FALSE;
11696     thinkOutput[0] = NULLCHAR;
11697     lastHint[0] = NULLCHAR;
11698     ClearGameInfo(&gameInfo);
11699     gameInfo.variant = StringToVariant(appData.variant);
11700     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11701     ics_user_moved = ics_clock_paused = FALSE;
11702     ics_getting_history = H_FALSE;
11703     ics_gamenum = -1;
11704     white_holding[0] = black_holding[0] = NULLCHAR;
11705     ClearProgramStats();
11706     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11707
11708     ResetFrontEnd();
11709     ClearHighlights();
11710     flipView = appData.flipView;
11711     ClearPremoveHighlights();
11712     gotPremove = FALSE;
11713     alarmSounded = FALSE;
11714     killX = killY = -1; // [HGM] lion
11715
11716     GameEnds(EndOfFile, NULL, GE_PLAYER);
11717     if(appData.serverMovesName != NULL) {
11718         /* [HGM] prepare to make moves file for broadcasting */
11719         clock_t t = clock();
11720         if(serverMoves != NULL) fclose(serverMoves);
11721         serverMoves = fopen(appData.serverMovesName, "r");
11722         if(serverMoves != NULL) {
11723             fclose(serverMoves);
11724             /* delay 15 sec before overwriting, so all clients can see end */
11725             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11726         }
11727         serverMoves = fopen(appData.serverMovesName, "w");
11728     }
11729
11730     ExitAnalyzeMode();
11731     gameMode = BeginningOfGame;
11732     ModeHighlight();
11733     if(appData.icsActive) gameInfo.variant = VariantNormal;
11734     currentMove = forwardMostMove = backwardMostMove = 0;
11735     MarkTargetSquares(1);
11736     InitPosition(redraw);
11737     for (i = 0; i < MAX_MOVES; i++) {
11738         if (commentList[i] != NULL) {
11739             free(commentList[i]);
11740             commentList[i] = NULL;
11741         }
11742     }
11743     ResetClocks();
11744     timeRemaining[0][0] = whiteTimeRemaining;
11745     timeRemaining[1][0] = blackTimeRemaining;
11746
11747     if (first.pr == NoProc) {
11748         StartChessProgram(&first);
11749     }
11750     if (init) {
11751             InitChessProgram(&first, startedFromSetupPosition);
11752     }
11753     DisplayTitle("");
11754     DisplayMessage("", "");
11755     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11756     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11757     ClearMap();        // [HGM] exclude: invalidate map
11758 }
11759
11760 void
11761 AutoPlayGameLoop ()
11762 {
11763     for (;;) {
11764         if (!AutoPlayOneMove())
11765           return;
11766         if (matchMode || appData.timeDelay == 0)
11767           continue;
11768         if (appData.timeDelay < 0)
11769           return;
11770         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11771         break;
11772     }
11773 }
11774
11775 void
11776 AnalyzeNextGame()
11777 {
11778     ReloadGame(1); // next game
11779 }
11780
11781 int
11782 AutoPlayOneMove ()
11783 {
11784     int fromX, fromY, toX, toY;
11785
11786     if (appData.debugMode) {
11787       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11788     }
11789
11790     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11791       return FALSE;
11792
11793     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11794       pvInfoList[currentMove].depth = programStats.depth;
11795       pvInfoList[currentMove].score = programStats.score;
11796       pvInfoList[currentMove].time  = 0;
11797       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11798       else { // append analysis of final position as comment
11799         char buf[MSG_SIZ];
11800         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11801         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11802       }
11803       programStats.depth = 0;
11804     }
11805
11806     if (currentMove >= forwardMostMove) {
11807       if(gameMode == AnalyzeFile) {
11808           if(appData.loadGameIndex == -1) {
11809             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
11810           ScheduleDelayedEvent(AnalyzeNextGame, 10);
11811           } else {
11812           ExitAnalyzeMode(); SendToProgram("force\n", &first);
11813         }
11814       }
11815 //      gameMode = EndOfGame;
11816 //      ModeHighlight();
11817
11818       /* [AS] Clear current move marker at the end of a game */
11819       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
11820
11821       return FALSE;
11822     }
11823
11824     toX = moveList[currentMove][2] - AAA;
11825     toY = moveList[currentMove][3] - ONE;
11826
11827     if (moveList[currentMove][1] == '@') {
11828         if (appData.highlightLastMove) {
11829             SetHighlights(-1, -1, toX, toY);
11830         }
11831     } else {
11832         int viaX = moveList[currentMove][5] - AAA;
11833         int viaY = moveList[currentMove][6] - ONE;
11834         fromX = moveList[currentMove][0] - AAA;
11835         fromY = moveList[currentMove][1] - ONE;
11836
11837         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
11838
11839         if(moveList[currentMove][4] == ';') { // multi-leg
11840             ChessSquare piece = boards[currentMove][viaY][viaX];
11841             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
11842             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
11843             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
11844             boards[currentMove][viaY][viaX] = piece;
11845         } else
11846         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11847
11848         if (appData.highlightLastMove) {
11849             SetHighlights(fromX, fromY, toX, toY);
11850         }
11851     }
11852     DisplayMove(currentMove);
11853     SendMoveToProgram(currentMove++, &first);
11854     DisplayBothClocks();
11855     DrawPosition(FALSE, boards[currentMove]);
11856     // [HGM] PV info: always display, routine tests if empty
11857     DisplayComment(currentMove - 1, commentList[currentMove]);
11858     return TRUE;
11859 }
11860
11861
11862 int
11863 LoadGameOneMove (ChessMove readAhead)
11864 {
11865     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
11866     char promoChar = NULLCHAR;
11867     ChessMove moveType;
11868     char move[MSG_SIZ];
11869     char *p, *q;
11870
11871     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
11872         gameMode != AnalyzeMode && gameMode != Training) {
11873         gameFileFP = NULL;
11874         return FALSE;
11875     }
11876
11877     yyboardindex = forwardMostMove;
11878     if (readAhead != EndOfFile) {
11879       moveType = readAhead;
11880     } else {
11881       if (gameFileFP == NULL)
11882           return FALSE;
11883       moveType = (ChessMove) Myylex();
11884     }
11885
11886     done = FALSE;
11887     switch (moveType) {
11888       case Comment:
11889         if (appData.debugMode)
11890           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11891         p = yy_text;
11892
11893         /* append the comment but don't display it */
11894         AppendComment(currentMove, p, FALSE);
11895         return TRUE;
11896
11897       case WhiteCapturesEnPassant:
11898       case BlackCapturesEnPassant:
11899       case WhitePromotion:
11900       case BlackPromotion:
11901       case WhiteNonPromotion:
11902       case BlackNonPromotion:
11903       case NormalMove:
11904       case FirstLeg:
11905       case WhiteKingSideCastle:
11906       case WhiteQueenSideCastle:
11907       case BlackKingSideCastle:
11908       case BlackQueenSideCastle:
11909       case WhiteKingSideCastleWild:
11910       case WhiteQueenSideCastleWild:
11911       case BlackKingSideCastleWild:
11912       case BlackQueenSideCastleWild:
11913       /* PUSH Fabien */
11914       case WhiteHSideCastleFR:
11915       case WhiteASideCastleFR:
11916       case BlackHSideCastleFR:
11917       case BlackASideCastleFR:
11918       /* POP Fabien */
11919         if (appData.debugMode)
11920           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11921         fromX = currentMoveString[0] - AAA;
11922         fromY = currentMoveString[1] - ONE;
11923         toX = currentMoveString[2] - AAA;
11924         toY = currentMoveString[3] - ONE;
11925         promoChar = currentMoveString[4];
11926         if(promoChar == ';') promoChar = NULLCHAR;
11927         break;
11928
11929       case WhiteDrop:
11930       case BlackDrop:
11931         if (appData.debugMode)
11932           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11933         fromX = moveType == WhiteDrop ?
11934           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11935         (int) CharToPiece(ToLower(currentMoveString[0]));
11936         fromY = DROP_RANK;
11937         toX = currentMoveString[2] - AAA;
11938         toY = currentMoveString[3] - ONE;
11939         break;
11940
11941       case WhiteWins:
11942       case BlackWins:
11943       case GameIsDrawn:
11944       case GameUnfinished:
11945         if (appData.debugMode)
11946           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11947         p = strchr(yy_text, '{');
11948         if (p == NULL) p = strchr(yy_text, '(');
11949         if (p == NULL) {
11950             p = yy_text;
11951             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11952         } else {
11953             q = strchr(p, *p == '{' ? '}' : ')');
11954             if (q != NULL) *q = NULLCHAR;
11955             p++;
11956         }
11957         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11958         GameEnds(moveType, p, GE_FILE);
11959         done = TRUE;
11960         if (cmailMsgLoaded) {
11961             ClearHighlights();
11962             flipView = WhiteOnMove(currentMove);
11963             if (moveType == GameUnfinished) flipView = !flipView;
11964             if (appData.debugMode)
11965               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11966         }
11967         break;
11968
11969       case EndOfFile:
11970         if (appData.debugMode)
11971           fprintf(debugFP, "Parser hit end of file\n");
11972         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11973           case MT_NONE:
11974           case MT_CHECK:
11975             break;
11976           case MT_CHECKMATE:
11977           case MT_STAINMATE:
11978             if (WhiteOnMove(currentMove)) {
11979                 GameEnds(BlackWins, "Black mates", GE_FILE);
11980             } else {
11981                 GameEnds(WhiteWins, "White mates", GE_FILE);
11982             }
11983             break;
11984           case MT_STALEMATE:
11985             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11986             break;
11987         }
11988         done = TRUE;
11989         break;
11990
11991       case MoveNumberOne:
11992         if (lastLoadGameStart == GNUChessGame) {
11993             /* GNUChessGames have numbers, but they aren't move numbers */
11994             if (appData.debugMode)
11995               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11996                       yy_text, (int) moveType);
11997             return LoadGameOneMove(EndOfFile); /* tail recursion */
11998         }
11999         /* else fall thru */
12000
12001       case XBoardGame:
12002       case GNUChessGame:
12003       case PGNTag:
12004         /* Reached start of next game in file */
12005         if (appData.debugMode)
12006           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12007         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12008           case MT_NONE:
12009           case MT_CHECK:
12010             break;
12011           case MT_CHECKMATE:
12012           case MT_STAINMATE:
12013             if (WhiteOnMove(currentMove)) {
12014                 GameEnds(BlackWins, "Black mates", GE_FILE);
12015             } else {
12016                 GameEnds(WhiteWins, "White mates", GE_FILE);
12017             }
12018             break;
12019           case MT_STALEMATE:
12020             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12021             break;
12022         }
12023         done = TRUE;
12024         break;
12025
12026       case PositionDiagram:     /* should not happen; ignore */
12027       case ElapsedTime:         /* ignore */
12028       case NAG:                 /* ignore */
12029         if (appData.debugMode)
12030           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12031                   yy_text, (int) moveType);
12032         return LoadGameOneMove(EndOfFile); /* tail recursion */
12033
12034       case IllegalMove:
12035         if (appData.testLegality) {
12036             if (appData.debugMode)
12037               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12038             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12039                     (forwardMostMove / 2) + 1,
12040                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12041             DisplayError(move, 0);
12042             done = TRUE;
12043         } else {
12044             if (appData.debugMode)
12045               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12046                       yy_text, currentMoveString);
12047             fromX = currentMoveString[0] - AAA;
12048             fromY = currentMoveString[1] - ONE;
12049             toX = currentMoveString[2] - AAA;
12050             toY = currentMoveString[3] - ONE;
12051             promoChar = currentMoveString[4];
12052         }
12053         break;
12054
12055       case AmbiguousMove:
12056         if (appData.debugMode)
12057           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12058         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12059                 (forwardMostMove / 2) + 1,
12060                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12061         DisplayError(move, 0);
12062         done = TRUE;
12063         break;
12064
12065       default:
12066       case ImpossibleMove:
12067         if (appData.debugMode)
12068           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12069         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12070                 (forwardMostMove / 2) + 1,
12071                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12072         DisplayError(move, 0);
12073         done = TRUE;
12074         break;
12075     }
12076
12077     if (done) {
12078         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12079             DrawPosition(FALSE, boards[currentMove]);
12080             DisplayBothClocks();
12081             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12082               DisplayComment(currentMove - 1, commentList[currentMove]);
12083         }
12084         (void) StopLoadGameTimer();
12085         gameFileFP = NULL;
12086         cmailOldMove = forwardMostMove;
12087         return FALSE;
12088     } else {
12089         /* currentMoveString is set as a side-effect of yylex */
12090
12091         thinkOutput[0] = NULLCHAR;
12092         MakeMove(fromX, fromY, toX, toY, promoChar);
12093         killX = killY = -1; // [HGM] lion: used up
12094         currentMove = forwardMostMove;
12095         return TRUE;
12096     }
12097 }
12098
12099 /* Load the nth game from the given file */
12100 int
12101 LoadGameFromFile (char *filename, int n, char *title, int useList)
12102 {
12103     FILE *f;
12104     char buf[MSG_SIZ];
12105
12106     if (strcmp(filename, "-") == 0) {
12107         f = stdin;
12108         title = "stdin";
12109     } else {
12110         f = fopen(filename, "rb");
12111         if (f == NULL) {
12112           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12113             DisplayError(buf, errno);
12114             return FALSE;
12115         }
12116     }
12117     if (fseek(f, 0, 0) == -1) {
12118         /* f is not seekable; probably a pipe */
12119         useList = FALSE;
12120     }
12121     if (useList && n == 0) {
12122         int error = GameListBuild(f);
12123         if (error) {
12124             DisplayError(_("Cannot build game list"), error);
12125         } else if (!ListEmpty(&gameList) &&
12126                    ((ListGame *) gameList.tailPred)->number > 1) {
12127             GameListPopUp(f, title);
12128             return TRUE;
12129         }
12130         GameListDestroy();
12131         n = 1;
12132     }
12133     if (n == 0) n = 1;
12134     return LoadGame(f, n, title, FALSE);
12135 }
12136
12137
12138 void
12139 MakeRegisteredMove ()
12140 {
12141     int fromX, fromY, toX, toY;
12142     char promoChar;
12143     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12144         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12145           case CMAIL_MOVE:
12146           case CMAIL_DRAW:
12147             if (appData.debugMode)
12148               fprintf(debugFP, "Restoring %s for game %d\n",
12149                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12150
12151             thinkOutput[0] = NULLCHAR;
12152             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12153             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12154             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12155             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12156             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12157             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12158             MakeMove(fromX, fromY, toX, toY, promoChar);
12159             ShowMove(fromX, fromY, toX, toY);
12160
12161             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12162               case MT_NONE:
12163               case MT_CHECK:
12164                 break;
12165
12166               case MT_CHECKMATE:
12167               case MT_STAINMATE:
12168                 if (WhiteOnMove(currentMove)) {
12169                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12170                 } else {
12171                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12172                 }
12173                 break;
12174
12175               case MT_STALEMATE:
12176                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12177                 break;
12178             }
12179
12180             break;
12181
12182           case CMAIL_RESIGN:
12183             if (WhiteOnMove(currentMove)) {
12184                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12185             } else {
12186                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12187             }
12188             break;
12189
12190           case CMAIL_ACCEPT:
12191             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12192             break;
12193
12194           default:
12195             break;
12196         }
12197     }
12198
12199     return;
12200 }
12201
12202 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12203 int
12204 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12205 {
12206     int retVal;
12207
12208     if (gameNumber > nCmailGames) {
12209         DisplayError(_("No more games in this message"), 0);
12210         return FALSE;
12211     }
12212     if (f == lastLoadGameFP) {
12213         int offset = gameNumber - lastLoadGameNumber;
12214         if (offset == 0) {
12215             cmailMsg[0] = NULLCHAR;
12216             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12217                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12218                 nCmailMovesRegistered--;
12219             }
12220             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12221             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12222                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12223             }
12224         } else {
12225             if (! RegisterMove()) return FALSE;
12226         }
12227     }
12228
12229     retVal = LoadGame(f, gameNumber, title, useList);
12230
12231     /* Make move registered during previous look at this game, if any */
12232     MakeRegisteredMove();
12233
12234     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12235         commentList[currentMove]
12236           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12237         DisplayComment(currentMove - 1, commentList[currentMove]);
12238     }
12239
12240     return retVal;
12241 }
12242
12243 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12244 int
12245 ReloadGame (int offset)
12246 {
12247     int gameNumber = lastLoadGameNumber + offset;
12248     if (lastLoadGameFP == NULL) {
12249         DisplayError(_("No game has been loaded yet"), 0);
12250         return FALSE;
12251     }
12252     if (gameNumber <= 0) {
12253         DisplayError(_("Can't back up any further"), 0);
12254         return FALSE;
12255     }
12256     if (cmailMsgLoaded) {
12257         return CmailLoadGame(lastLoadGameFP, gameNumber,
12258                              lastLoadGameTitle, lastLoadGameUseList);
12259     } else {
12260         return LoadGame(lastLoadGameFP, gameNumber,
12261                         lastLoadGameTitle, lastLoadGameUseList);
12262     }
12263 }
12264
12265 int keys[EmptySquare+1];
12266
12267 int
12268 PositionMatches (Board b1, Board b2)
12269 {
12270     int r, f, sum=0;
12271     switch(appData.searchMode) {
12272         case 1: return CompareWithRights(b1, b2);
12273         case 2:
12274             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12275                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12276             }
12277             return TRUE;
12278         case 3:
12279             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12280               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12281                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12282             }
12283             return sum==0;
12284         case 4:
12285             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12286                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12287             }
12288             return sum==0;
12289     }
12290     return TRUE;
12291 }
12292
12293 #define Q_PROMO  4
12294 #define Q_EP     3
12295 #define Q_BCASTL 2
12296 #define Q_WCASTL 1
12297
12298 int pieceList[256], quickBoard[256];
12299 ChessSquare pieceType[256] = { EmptySquare };
12300 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12301 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12302 int soughtTotal, turn;
12303 Boolean epOK, flipSearch;
12304
12305 typedef struct {
12306     unsigned char piece, to;
12307 } Move;
12308
12309 #define DSIZE (250000)
12310
12311 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12312 Move *moveDatabase = initialSpace;
12313 unsigned int movePtr, dataSize = DSIZE;
12314
12315 int
12316 MakePieceList (Board board, int *counts)
12317 {
12318     int r, f, n=Q_PROMO, total=0;
12319     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12320     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12321         int sq = f + (r<<4);
12322         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12323             quickBoard[sq] = ++n;
12324             pieceList[n] = sq;
12325             pieceType[n] = board[r][f];
12326             counts[board[r][f]]++;
12327             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12328             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12329             total++;
12330         }
12331     }
12332     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12333     return total;
12334 }
12335
12336 void
12337 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12338 {
12339     int sq = fromX + (fromY<<4);
12340     int piece = quickBoard[sq], rook;
12341     quickBoard[sq] = 0;
12342     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12343     if(piece == pieceList[1] && fromY == toY) {
12344       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12345         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12346         moveDatabase[movePtr++].piece = Q_WCASTL;
12347         quickBoard[sq] = piece;
12348         piece = quickBoard[from]; quickBoard[from] = 0;
12349         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12350       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12351         quickBoard[sq] = 0; // remove Rook
12352         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12353         moveDatabase[movePtr++].piece = Q_WCASTL;
12354         quickBoard[sq] = pieceList[1]; // put King
12355         piece = rook;
12356         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12357       }
12358     } else
12359     if(piece == pieceList[2] && fromY == toY) {
12360       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12361         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12362         moveDatabase[movePtr++].piece = Q_BCASTL;
12363         quickBoard[sq] = piece;
12364         piece = quickBoard[from]; quickBoard[from] = 0;
12365         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12366       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12367         quickBoard[sq] = 0; // remove Rook
12368         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12369         moveDatabase[movePtr++].piece = Q_BCASTL;
12370         quickBoard[sq] = pieceList[2]; // put King
12371         piece = rook;
12372         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12373       }
12374     } else
12375     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12376         quickBoard[(fromY<<4)+toX] = 0;
12377         moveDatabase[movePtr].piece = Q_EP;
12378         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12379         moveDatabase[movePtr].to = sq;
12380     } else
12381     if(promoPiece != pieceType[piece]) {
12382         moveDatabase[movePtr++].piece = Q_PROMO;
12383         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12384     }
12385     moveDatabase[movePtr].piece = piece;
12386     quickBoard[sq] = piece;
12387     movePtr++;
12388 }
12389
12390 int
12391 PackGame (Board board)
12392 {
12393     Move *newSpace = NULL;
12394     moveDatabase[movePtr].piece = 0; // terminate previous game
12395     if(movePtr > dataSize) {
12396         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12397         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12398         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12399         if(newSpace) {
12400             int i;
12401             Move *p = moveDatabase, *q = newSpace;
12402             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12403             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12404             moveDatabase = newSpace;
12405         } else { // calloc failed, we must be out of memory. Too bad...
12406             dataSize = 0; // prevent calloc events for all subsequent games
12407             return 0;     // and signal this one isn't cached
12408         }
12409     }
12410     movePtr++;
12411     MakePieceList(board, counts);
12412     return movePtr;
12413 }
12414
12415 int
12416 QuickCompare (Board board, int *minCounts, int *maxCounts)
12417 {   // compare according to search mode
12418     int r, f;
12419     switch(appData.searchMode)
12420     {
12421       case 1: // exact position match
12422         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12423         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12424             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12425         }
12426         break;
12427       case 2: // can have extra material on empty squares
12428         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12429             if(board[r][f] == EmptySquare) continue;
12430             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12431         }
12432         break;
12433       case 3: // material with exact Pawn structure
12434         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12435             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12436             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12437         } // fall through to material comparison
12438       case 4: // exact material
12439         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12440         break;
12441       case 6: // material range with given imbalance
12442         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12443         // fall through to range comparison
12444       case 5: // material range
12445         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12446     }
12447     return TRUE;
12448 }
12449
12450 int
12451 QuickScan (Board board, Move *move)
12452 {   // reconstruct game,and compare all positions in it
12453     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12454     do {
12455         int piece = move->piece;
12456         int to = move->to, from = pieceList[piece];
12457         if(found < 0) { // if already found just scan to game end for final piece count
12458           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12459            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12460            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12461                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12462             ) {
12463             static int lastCounts[EmptySquare+1];
12464             int i;
12465             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12466             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12467           } else stretch = 0;
12468           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12469           if(found >= 0 && !appData.minPieces) return found;
12470         }
12471         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12472           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12473           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12474             piece = (++move)->piece;
12475             from = pieceList[piece];
12476             counts[pieceType[piece]]--;
12477             pieceType[piece] = (ChessSquare) move->to;
12478             counts[move->to]++;
12479           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12480             counts[pieceType[quickBoard[to]]]--;
12481             quickBoard[to] = 0; total--;
12482             move++;
12483             continue;
12484           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12485             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12486             from  = pieceList[piece]; // so this must be King
12487             quickBoard[from] = 0;
12488             pieceList[piece] = to;
12489             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12490             quickBoard[from] = 0; // rook
12491             quickBoard[to] = piece;
12492             to = move->to; piece = move->piece;
12493             goto aftercastle;
12494           }
12495         }
12496         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12497         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12498         quickBoard[from] = 0;
12499       aftercastle:
12500         quickBoard[to] = piece;
12501         pieceList[piece] = to;
12502         cnt++; turn ^= 3;
12503         move++;
12504     } while(1);
12505 }
12506
12507 void
12508 InitSearch ()
12509 {
12510     int r, f;
12511     flipSearch = FALSE;
12512     CopyBoard(soughtBoard, boards[currentMove]);
12513     soughtTotal = MakePieceList(soughtBoard, maxSought);
12514     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12515     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12516     CopyBoard(reverseBoard, boards[currentMove]);
12517     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12518         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12519         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12520         reverseBoard[r][f] = piece;
12521     }
12522     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12523     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12524     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12525                  || (boards[currentMove][CASTLING][2] == NoRights ||
12526                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12527                  && (boards[currentMove][CASTLING][5] == NoRights ||
12528                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12529       ) {
12530         flipSearch = TRUE;
12531         CopyBoard(flipBoard, soughtBoard);
12532         CopyBoard(rotateBoard, reverseBoard);
12533         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12534             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12535             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12536         }
12537     }
12538     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12539     if(appData.searchMode >= 5) {
12540         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12541         MakePieceList(soughtBoard, minSought);
12542         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12543     }
12544     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12545         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12546 }
12547
12548 GameInfo dummyInfo;
12549 static int creatingBook;
12550
12551 int
12552 GameContainsPosition (FILE *f, ListGame *lg)
12553 {
12554     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12555     int fromX, fromY, toX, toY;
12556     char promoChar;
12557     static int initDone=FALSE;
12558
12559     // weed out games based on numerical tag comparison
12560     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12561     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12562     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12563     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12564     if(!initDone) {
12565         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12566         initDone = TRUE;
12567     }
12568     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12569     else CopyBoard(boards[scratch], initialPosition); // default start position
12570     if(lg->moves) {
12571         turn = btm + 1;
12572         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12573         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12574     }
12575     if(btm) plyNr++;
12576     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12577     fseek(f, lg->offset, 0);
12578     yynewfile(f);
12579     while(1) {
12580         yyboardindex = scratch;
12581         quickFlag = plyNr+1;
12582         next = Myylex();
12583         quickFlag = 0;
12584         switch(next) {
12585             case PGNTag:
12586                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12587             default:
12588                 continue;
12589
12590             case XBoardGame:
12591             case GNUChessGame:
12592                 if(plyNr) return -1; // after we have seen moves, this is for new game
12593               continue;
12594
12595             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12596             case ImpossibleMove:
12597             case WhiteWins: // game ends here with these four
12598             case BlackWins:
12599             case GameIsDrawn:
12600             case GameUnfinished:
12601                 return -1;
12602
12603             case IllegalMove:
12604                 if(appData.testLegality) return -1;
12605             case WhiteCapturesEnPassant:
12606             case BlackCapturesEnPassant:
12607             case WhitePromotion:
12608             case BlackPromotion:
12609             case WhiteNonPromotion:
12610             case BlackNonPromotion:
12611             case NormalMove:
12612             case FirstLeg:
12613             case WhiteKingSideCastle:
12614             case WhiteQueenSideCastle:
12615             case BlackKingSideCastle:
12616             case BlackQueenSideCastle:
12617             case WhiteKingSideCastleWild:
12618             case WhiteQueenSideCastleWild:
12619             case BlackKingSideCastleWild:
12620             case BlackQueenSideCastleWild:
12621             case WhiteHSideCastleFR:
12622             case WhiteASideCastleFR:
12623             case BlackHSideCastleFR:
12624             case BlackASideCastleFR:
12625                 fromX = currentMoveString[0] - AAA;
12626                 fromY = currentMoveString[1] - ONE;
12627                 toX = currentMoveString[2] - AAA;
12628                 toY = currentMoveString[3] - ONE;
12629                 promoChar = currentMoveString[4];
12630                 break;
12631             case WhiteDrop:
12632             case BlackDrop:
12633                 fromX = next == WhiteDrop ?
12634                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12635                   (int) CharToPiece(ToLower(currentMoveString[0]));
12636                 fromY = DROP_RANK;
12637                 toX = currentMoveString[2] - AAA;
12638                 toY = currentMoveString[3] - ONE;
12639                 promoChar = 0;
12640                 break;
12641         }
12642         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12643         plyNr++;
12644         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12645         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12646         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12647         if(appData.findMirror) {
12648             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12649             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12650         }
12651     }
12652 }
12653
12654 /* Load the nth game from open file f */
12655 int
12656 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12657 {
12658     ChessMove cm;
12659     char buf[MSG_SIZ];
12660     int gn = gameNumber;
12661     ListGame *lg = NULL;
12662     int numPGNTags = 0;
12663     int err, pos = -1;
12664     GameMode oldGameMode;
12665     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12666
12667     if (appData.debugMode)
12668         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12669
12670     if (gameMode == Training )
12671         SetTrainingModeOff();
12672
12673     oldGameMode = gameMode;
12674     if (gameMode != BeginningOfGame) {
12675       Reset(FALSE, TRUE);
12676     }
12677     killX = killY = -1; // [HGM] lion: in case we did not Reset
12678
12679     gameFileFP = f;
12680     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12681         fclose(lastLoadGameFP);
12682     }
12683
12684     if (useList) {
12685         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12686
12687         if (lg) {
12688             fseek(f, lg->offset, 0);
12689             GameListHighlight(gameNumber);
12690             pos = lg->position;
12691             gn = 1;
12692         }
12693         else {
12694             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12695               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12696             else
12697             DisplayError(_("Game number out of range"), 0);
12698             return FALSE;
12699         }
12700     } else {
12701         GameListDestroy();
12702         if (fseek(f, 0, 0) == -1) {
12703             if (f == lastLoadGameFP ?
12704                 gameNumber == lastLoadGameNumber + 1 :
12705                 gameNumber == 1) {
12706                 gn = 1;
12707             } else {
12708                 DisplayError(_("Can't seek on game file"), 0);
12709                 return FALSE;
12710             }
12711         }
12712     }
12713     lastLoadGameFP = f;
12714     lastLoadGameNumber = gameNumber;
12715     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12716     lastLoadGameUseList = useList;
12717
12718     yynewfile(f);
12719
12720     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12721       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12722                 lg->gameInfo.black);
12723             DisplayTitle(buf);
12724     } else if (*title != NULLCHAR) {
12725         if (gameNumber > 1) {
12726           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12727             DisplayTitle(buf);
12728         } else {
12729             DisplayTitle(title);
12730         }
12731     }
12732
12733     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12734         gameMode = PlayFromGameFile;
12735         ModeHighlight();
12736     }
12737
12738     currentMove = forwardMostMove = backwardMostMove = 0;
12739     CopyBoard(boards[0], initialPosition);
12740     StopClocks();
12741
12742     /*
12743      * Skip the first gn-1 games in the file.
12744      * Also skip over anything that precedes an identifiable
12745      * start of game marker, to avoid being confused by
12746      * garbage at the start of the file.  Currently
12747      * recognized start of game markers are the move number "1",
12748      * the pattern "gnuchess .* game", the pattern
12749      * "^[#;%] [^ ]* game file", and a PGN tag block.
12750      * A game that starts with one of the latter two patterns
12751      * will also have a move number 1, possibly
12752      * following a position diagram.
12753      * 5-4-02: Let's try being more lenient and allowing a game to
12754      * start with an unnumbered move.  Does that break anything?
12755      */
12756     cm = lastLoadGameStart = EndOfFile;
12757     while (gn > 0) {
12758         yyboardindex = forwardMostMove;
12759         cm = (ChessMove) Myylex();
12760         switch (cm) {
12761           case EndOfFile:
12762             if (cmailMsgLoaded) {
12763                 nCmailGames = CMAIL_MAX_GAMES - gn;
12764             } else {
12765                 Reset(TRUE, TRUE);
12766                 DisplayError(_("Game not found in file"), 0);
12767             }
12768             return FALSE;
12769
12770           case GNUChessGame:
12771           case XBoardGame:
12772             gn--;
12773             lastLoadGameStart = cm;
12774             break;
12775
12776           case MoveNumberOne:
12777             switch (lastLoadGameStart) {
12778               case GNUChessGame:
12779               case XBoardGame:
12780               case PGNTag:
12781                 break;
12782               case MoveNumberOne:
12783               case EndOfFile:
12784                 gn--;           /* count this game */
12785                 lastLoadGameStart = cm;
12786                 break;
12787               default:
12788                 /* impossible */
12789                 break;
12790             }
12791             break;
12792
12793           case PGNTag:
12794             switch (lastLoadGameStart) {
12795               case GNUChessGame:
12796               case PGNTag:
12797               case MoveNumberOne:
12798               case EndOfFile:
12799                 gn--;           /* count this game */
12800                 lastLoadGameStart = cm;
12801                 break;
12802               case XBoardGame:
12803                 lastLoadGameStart = cm; /* game counted already */
12804                 break;
12805               default:
12806                 /* impossible */
12807                 break;
12808             }
12809             if (gn > 0) {
12810                 do {
12811                     yyboardindex = forwardMostMove;
12812                     cm = (ChessMove) Myylex();
12813                 } while (cm == PGNTag || cm == Comment);
12814             }
12815             break;
12816
12817           case WhiteWins:
12818           case BlackWins:
12819           case GameIsDrawn:
12820             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
12821                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
12822                     != CMAIL_OLD_RESULT) {
12823                     nCmailResults ++ ;
12824                     cmailResult[  CMAIL_MAX_GAMES
12825                                 - gn - 1] = CMAIL_OLD_RESULT;
12826                 }
12827             }
12828             break;
12829
12830           case NormalMove:
12831           case FirstLeg:
12832             /* Only a NormalMove can be at the start of a game
12833              * without a position diagram. */
12834             if (lastLoadGameStart == EndOfFile ) {
12835               gn--;
12836               lastLoadGameStart = MoveNumberOne;
12837             }
12838             break;
12839
12840           default:
12841             break;
12842         }
12843     }
12844
12845     if (appData.debugMode)
12846       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
12847
12848     if (cm == XBoardGame) {
12849         /* Skip any header junk before position diagram and/or move 1 */
12850         for (;;) {
12851             yyboardindex = forwardMostMove;
12852             cm = (ChessMove) Myylex();
12853
12854             if (cm == EndOfFile ||
12855                 cm == GNUChessGame || cm == XBoardGame) {
12856                 /* Empty game; pretend end-of-file and handle later */
12857                 cm = EndOfFile;
12858                 break;
12859             }
12860
12861             if (cm == MoveNumberOne || cm == PositionDiagram ||
12862                 cm == PGNTag || cm == Comment)
12863               break;
12864         }
12865     } else if (cm == GNUChessGame) {
12866         if (gameInfo.event != NULL) {
12867             free(gameInfo.event);
12868         }
12869         gameInfo.event = StrSave(yy_text);
12870     }
12871
12872     startedFromSetupPosition = FALSE;
12873     while (cm == PGNTag) {
12874         if (appData.debugMode)
12875           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
12876         err = ParsePGNTag(yy_text, &gameInfo);
12877         if (!err) numPGNTags++;
12878
12879         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
12880         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
12881             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
12882             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
12883             InitPosition(TRUE);
12884             oldVariant = gameInfo.variant;
12885             if (appData.debugMode)
12886               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
12887         }
12888
12889
12890         if (gameInfo.fen != NULL) {
12891           Board initial_position;
12892           startedFromSetupPosition = TRUE;
12893           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
12894             Reset(TRUE, TRUE);
12895             DisplayError(_("Bad FEN position in file"), 0);
12896             return FALSE;
12897           }
12898           CopyBoard(boards[0], initial_position);
12899           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
12900             CopyBoard(initialPosition, initial_position);
12901           if (blackPlaysFirst) {
12902             currentMove = forwardMostMove = backwardMostMove = 1;
12903             CopyBoard(boards[1], initial_position);
12904             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12905             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12906             timeRemaining[0][1] = whiteTimeRemaining;
12907             timeRemaining[1][1] = blackTimeRemaining;
12908             if (commentList[0] != NULL) {
12909               commentList[1] = commentList[0];
12910               commentList[0] = NULL;
12911             }
12912           } else {
12913             currentMove = forwardMostMove = backwardMostMove = 0;
12914           }
12915           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
12916           {   int i;
12917               initialRulePlies = FENrulePlies;
12918               for( i=0; i< nrCastlingRights; i++ )
12919                   initialRights[i] = initial_position[CASTLING][i];
12920           }
12921           yyboardindex = forwardMostMove;
12922           free(gameInfo.fen);
12923           gameInfo.fen = NULL;
12924         }
12925
12926         yyboardindex = forwardMostMove;
12927         cm = (ChessMove) Myylex();
12928
12929         /* Handle comments interspersed among the tags */
12930         while (cm == Comment) {
12931             char *p;
12932             if (appData.debugMode)
12933               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12934             p = yy_text;
12935             AppendComment(currentMove, p, FALSE);
12936             yyboardindex = forwardMostMove;
12937             cm = (ChessMove) Myylex();
12938         }
12939     }
12940
12941     /* don't rely on existence of Event tag since if game was
12942      * pasted from clipboard the Event tag may not exist
12943      */
12944     if (numPGNTags > 0){
12945         char *tags;
12946         if (gameInfo.variant == VariantNormal) {
12947           VariantClass v = StringToVariant(gameInfo.event);
12948           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
12949           if(v < VariantShogi) gameInfo.variant = v;
12950         }
12951         if (!matchMode) {
12952           if( appData.autoDisplayTags ) {
12953             tags = PGNTags(&gameInfo);
12954             TagsPopUp(tags, CmailMsg());
12955             free(tags);
12956           }
12957         }
12958     } else {
12959         /* Make something up, but don't display it now */
12960         SetGameInfo();
12961         TagsPopDown();
12962     }
12963
12964     if (cm == PositionDiagram) {
12965         int i, j;
12966         char *p;
12967         Board initial_position;
12968
12969         if (appData.debugMode)
12970           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12971
12972         if (!startedFromSetupPosition) {
12973             p = yy_text;
12974             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12975               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12976                 switch (*p) {
12977                   case '{':
12978                   case '[':
12979                   case '-':
12980                   case ' ':
12981                   case '\t':
12982                   case '\n':
12983                   case '\r':
12984                     break;
12985                   default:
12986                     initial_position[i][j++] = CharToPiece(*p);
12987                     break;
12988                 }
12989             while (*p == ' ' || *p == '\t' ||
12990                    *p == '\n' || *p == '\r') p++;
12991
12992             if (strncmp(p, "black", strlen("black"))==0)
12993               blackPlaysFirst = TRUE;
12994             else
12995               blackPlaysFirst = FALSE;
12996             startedFromSetupPosition = TRUE;
12997
12998             CopyBoard(boards[0], initial_position);
12999             if (blackPlaysFirst) {
13000                 currentMove = forwardMostMove = backwardMostMove = 1;
13001                 CopyBoard(boards[1], initial_position);
13002                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13003                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13004                 timeRemaining[0][1] = whiteTimeRemaining;
13005                 timeRemaining[1][1] = blackTimeRemaining;
13006                 if (commentList[0] != NULL) {
13007                     commentList[1] = commentList[0];
13008                     commentList[0] = NULL;
13009                 }
13010             } else {
13011                 currentMove = forwardMostMove = backwardMostMove = 0;
13012             }
13013         }
13014         yyboardindex = forwardMostMove;
13015         cm = (ChessMove) Myylex();
13016     }
13017
13018   if(!creatingBook) {
13019     if (first.pr == NoProc) {
13020         StartChessProgram(&first);
13021     }
13022     InitChessProgram(&first, FALSE);
13023     SendToProgram("force\n", &first);
13024     if (startedFromSetupPosition) {
13025         SendBoard(&first, forwardMostMove);
13026     if (appData.debugMode) {
13027         fprintf(debugFP, "Load Game\n");
13028     }
13029         DisplayBothClocks();
13030     }
13031   }
13032
13033     /* [HGM] server: flag to write setup moves in broadcast file as one */
13034     loadFlag = appData.suppressLoadMoves;
13035
13036     while (cm == Comment) {
13037         char *p;
13038         if (appData.debugMode)
13039           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13040         p = yy_text;
13041         AppendComment(currentMove, p, FALSE);
13042         yyboardindex = forwardMostMove;
13043         cm = (ChessMove) Myylex();
13044     }
13045
13046     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13047         cm == WhiteWins || cm == BlackWins ||
13048         cm == GameIsDrawn || cm == GameUnfinished) {
13049         DisplayMessage("", _("No moves in game"));
13050         if (cmailMsgLoaded) {
13051             if (appData.debugMode)
13052               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13053             ClearHighlights();
13054             flipView = FALSE;
13055         }
13056         DrawPosition(FALSE, boards[currentMove]);
13057         DisplayBothClocks();
13058         gameMode = EditGame;
13059         ModeHighlight();
13060         gameFileFP = NULL;
13061         cmailOldMove = 0;
13062         return TRUE;
13063     }
13064
13065     // [HGM] PV info: routine tests if comment empty
13066     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13067         DisplayComment(currentMove - 1, commentList[currentMove]);
13068     }
13069     if (!matchMode && appData.timeDelay != 0)
13070       DrawPosition(FALSE, boards[currentMove]);
13071
13072     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13073       programStats.ok_to_send = 1;
13074     }
13075
13076     /* if the first token after the PGN tags is a move
13077      * and not move number 1, retrieve it from the parser
13078      */
13079     if (cm != MoveNumberOne)
13080         LoadGameOneMove(cm);
13081
13082     /* load the remaining moves from the file */
13083     while (LoadGameOneMove(EndOfFile)) {
13084       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13085       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13086     }
13087
13088     /* rewind to the start of the game */
13089     currentMove = backwardMostMove;
13090
13091     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13092
13093     if (oldGameMode == AnalyzeFile) {
13094       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13095       AnalyzeFileEvent();
13096     } else
13097     if (oldGameMode == AnalyzeMode) {
13098       AnalyzeFileEvent();
13099     }
13100
13101     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13102         long int w, b; // [HGM] adjourn: restore saved clock times
13103         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13104         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13105             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13106             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13107         }
13108     }
13109
13110     if(creatingBook) return TRUE;
13111     if (!matchMode && pos > 0) {
13112         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13113     } else
13114     if (matchMode || appData.timeDelay == 0) {
13115       ToEndEvent();
13116     } else if (appData.timeDelay > 0) {
13117       AutoPlayGameLoop();
13118     }
13119
13120     if (appData.debugMode)
13121         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13122
13123     loadFlag = 0; /* [HGM] true game starts */
13124     return TRUE;
13125 }
13126
13127 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13128 int
13129 ReloadPosition (int offset)
13130 {
13131     int positionNumber = lastLoadPositionNumber + offset;
13132     if (lastLoadPositionFP == NULL) {
13133         DisplayError(_("No position has been loaded yet"), 0);
13134         return FALSE;
13135     }
13136     if (positionNumber <= 0) {
13137         DisplayError(_("Can't back up any further"), 0);
13138         return FALSE;
13139     }
13140     return LoadPosition(lastLoadPositionFP, positionNumber,
13141                         lastLoadPositionTitle);
13142 }
13143
13144 /* Load the nth position from the given file */
13145 int
13146 LoadPositionFromFile (char *filename, int n, char *title)
13147 {
13148     FILE *f;
13149     char buf[MSG_SIZ];
13150
13151     if (strcmp(filename, "-") == 0) {
13152         return LoadPosition(stdin, n, "stdin");
13153     } else {
13154         f = fopen(filename, "rb");
13155         if (f == NULL) {
13156             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13157             DisplayError(buf, errno);
13158             return FALSE;
13159         } else {
13160             return LoadPosition(f, n, title);
13161         }
13162     }
13163 }
13164
13165 /* Load the nth position from the given open file, and close it */
13166 int
13167 LoadPosition (FILE *f, int positionNumber, char *title)
13168 {
13169     char *p, line[MSG_SIZ];
13170     Board initial_position;
13171     int i, j, fenMode, pn;
13172
13173     if (gameMode == Training )
13174         SetTrainingModeOff();
13175
13176     if (gameMode != BeginningOfGame) {
13177         Reset(FALSE, TRUE);
13178     }
13179     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13180         fclose(lastLoadPositionFP);
13181     }
13182     if (positionNumber == 0) positionNumber = 1;
13183     lastLoadPositionFP = f;
13184     lastLoadPositionNumber = positionNumber;
13185     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13186     if (first.pr == NoProc && !appData.noChessProgram) {
13187       StartChessProgram(&first);
13188       InitChessProgram(&first, FALSE);
13189     }
13190     pn = positionNumber;
13191     if (positionNumber < 0) {
13192         /* Negative position number means to seek to that byte offset */
13193         if (fseek(f, -positionNumber, 0) == -1) {
13194             DisplayError(_("Can't seek on position file"), 0);
13195             return FALSE;
13196         };
13197         pn = 1;
13198     } else {
13199         if (fseek(f, 0, 0) == -1) {
13200             if (f == lastLoadPositionFP ?
13201                 positionNumber == lastLoadPositionNumber + 1 :
13202                 positionNumber == 1) {
13203                 pn = 1;
13204             } else {
13205                 DisplayError(_("Can't seek on position file"), 0);
13206                 return FALSE;
13207             }
13208         }
13209     }
13210     /* See if this file is FEN or old-style xboard */
13211     if (fgets(line, MSG_SIZ, f) == NULL) {
13212         DisplayError(_("Position not found in file"), 0);
13213         return FALSE;
13214     }
13215     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
13216     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
13217
13218     if (pn >= 2) {
13219         if (fenMode || line[0] == '#') pn--;
13220         while (pn > 0) {
13221             /* skip positions before number pn */
13222             if (fgets(line, MSG_SIZ, f) == NULL) {
13223                 Reset(TRUE, TRUE);
13224                 DisplayError(_("Position not found in file"), 0);
13225                 return FALSE;
13226             }
13227             if (fenMode || line[0] == '#') pn--;
13228         }
13229     }
13230
13231     if (fenMode) {
13232         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13233             DisplayError(_("Bad FEN position in file"), 0);
13234             return FALSE;
13235         }
13236     } else {
13237         (void) fgets(line, MSG_SIZ, f);
13238         (void) fgets(line, MSG_SIZ, f);
13239
13240         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13241             (void) fgets(line, MSG_SIZ, f);
13242             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13243                 if (*p == ' ')
13244                   continue;
13245                 initial_position[i][j++] = CharToPiece(*p);
13246             }
13247         }
13248
13249         blackPlaysFirst = FALSE;
13250         if (!feof(f)) {
13251             (void) fgets(line, MSG_SIZ, f);
13252             if (strncmp(line, "black", strlen("black"))==0)
13253               blackPlaysFirst = TRUE;
13254         }
13255     }
13256     startedFromSetupPosition = TRUE;
13257
13258     CopyBoard(boards[0], initial_position);
13259     if (blackPlaysFirst) {
13260         currentMove = forwardMostMove = backwardMostMove = 1;
13261         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13262         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13263         CopyBoard(boards[1], initial_position);
13264         DisplayMessage("", _("Black to play"));
13265     } else {
13266         currentMove = forwardMostMove = backwardMostMove = 0;
13267         DisplayMessage("", _("White to play"));
13268     }
13269     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13270     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13271         SendToProgram("force\n", &first);
13272         SendBoard(&first, forwardMostMove);
13273     }
13274     if (appData.debugMode) {
13275 int i, j;
13276   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13277   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13278         fprintf(debugFP, "Load Position\n");
13279     }
13280
13281     if (positionNumber > 1) {
13282       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13283         DisplayTitle(line);
13284     } else {
13285         DisplayTitle(title);
13286     }
13287     gameMode = EditGame;
13288     ModeHighlight();
13289     ResetClocks();
13290     timeRemaining[0][1] = whiteTimeRemaining;
13291     timeRemaining[1][1] = blackTimeRemaining;
13292     DrawPosition(FALSE, boards[currentMove]);
13293
13294     return TRUE;
13295 }
13296
13297
13298 void
13299 CopyPlayerNameIntoFileName (char **dest, char *src)
13300 {
13301     while (*src != NULLCHAR && *src != ',') {
13302         if (*src == ' ') {
13303             *(*dest)++ = '_';
13304             src++;
13305         } else {
13306             *(*dest)++ = *src++;
13307         }
13308     }
13309 }
13310
13311 char *
13312 DefaultFileName (char *ext)
13313 {
13314     static char def[MSG_SIZ];
13315     char *p;
13316
13317     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13318         p = def;
13319         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13320         *p++ = '-';
13321         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13322         *p++ = '.';
13323         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13324     } else {
13325         def[0] = NULLCHAR;
13326     }
13327     return def;
13328 }
13329
13330 /* Save the current game to the given file */
13331 int
13332 SaveGameToFile (char *filename, int append)
13333 {
13334     FILE *f;
13335     char buf[MSG_SIZ];
13336     int result, i, t,tot=0;
13337
13338     if (strcmp(filename, "-") == 0) {
13339         return SaveGame(stdout, 0, NULL);
13340     } else {
13341         for(i=0; i<10; i++) { // upto 10 tries
13342              f = fopen(filename, append ? "a" : "w");
13343              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13344              if(f || errno != 13) break;
13345              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13346              tot += t;
13347         }
13348         if (f == NULL) {
13349             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13350             DisplayError(buf, errno);
13351             return FALSE;
13352         } else {
13353             safeStrCpy(buf, lastMsg, MSG_SIZ);
13354             DisplayMessage(_("Waiting for access to save file"), "");
13355             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13356             DisplayMessage(_("Saving game"), "");
13357             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13358             result = SaveGame(f, 0, NULL);
13359             DisplayMessage(buf, "");
13360             return result;
13361         }
13362     }
13363 }
13364
13365 char *
13366 SavePart (char *str)
13367 {
13368     static char buf[MSG_SIZ];
13369     char *p;
13370
13371     p = strchr(str, ' ');
13372     if (p == NULL) return str;
13373     strncpy(buf, str, p - str);
13374     buf[p - str] = NULLCHAR;
13375     return buf;
13376 }
13377
13378 #define PGN_MAX_LINE 75
13379
13380 #define PGN_SIDE_WHITE  0
13381 #define PGN_SIDE_BLACK  1
13382
13383 static int
13384 FindFirstMoveOutOfBook (int side)
13385 {
13386     int result = -1;
13387
13388     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13389         int index = backwardMostMove;
13390         int has_book_hit = 0;
13391
13392         if( (index % 2) != side ) {
13393             index++;
13394         }
13395
13396         while( index < forwardMostMove ) {
13397             /* Check to see if engine is in book */
13398             int depth = pvInfoList[index].depth;
13399             int score = pvInfoList[index].score;
13400             int in_book = 0;
13401
13402             if( depth <= 2 ) {
13403                 in_book = 1;
13404             }
13405             else if( score == 0 && depth == 63 ) {
13406                 in_book = 1; /* Zappa */
13407             }
13408             else if( score == 2 && depth == 99 ) {
13409                 in_book = 1; /* Abrok */
13410             }
13411
13412             has_book_hit += in_book;
13413
13414             if( ! in_book ) {
13415                 result = index;
13416
13417                 break;
13418             }
13419
13420             index += 2;
13421         }
13422     }
13423
13424     return result;
13425 }
13426
13427 void
13428 GetOutOfBookInfo (char * buf)
13429 {
13430     int oob[2];
13431     int i;
13432     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13433
13434     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13435     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13436
13437     *buf = '\0';
13438
13439     if( oob[0] >= 0 || oob[1] >= 0 ) {
13440         for( i=0; i<2; i++ ) {
13441             int idx = oob[i];
13442
13443             if( idx >= 0 ) {
13444                 if( i > 0 && oob[0] >= 0 ) {
13445                     strcat( buf, "   " );
13446                 }
13447
13448                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13449                 sprintf( buf+strlen(buf), "%s%.2f",
13450                     pvInfoList[idx].score >= 0 ? "+" : "",
13451                     pvInfoList[idx].score / 100.0 );
13452             }
13453         }
13454     }
13455 }
13456
13457 /* Save game in PGN style */
13458 static void
13459 SaveGamePGN2 (FILE *f)
13460 {
13461     int i, offset, linelen, newblock;
13462 //    char *movetext;
13463     char numtext[32];
13464     int movelen, numlen, blank;
13465     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13466
13467     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13468
13469     PrintPGNTags(f, &gameInfo);
13470
13471     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13472
13473     if (backwardMostMove > 0 || startedFromSetupPosition) {
13474         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13475         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13476         fprintf(f, "\n{--------------\n");
13477         PrintPosition(f, backwardMostMove);
13478         fprintf(f, "--------------}\n");
13479         free(fen);
13480     }
13481     else {
13482         /* [AS] Out of book annotation */
13483         if( appData.saveOutOfBookInfo ) {
13484             char buf[64];
13485
13486             GetOutOfBookInfo( buf );
13487
13488             if( buf[0] != '\0' ) {
13489                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13490             }
13491         }
13492
13493         fprintf(f, "\n");
13494     }
13495
13496     i = backwardMostMove;
13497     linelen = 0;
13498     newblock = TRUE;
13499
13500     while (i < forwardMostMove) {
13501         /* Print comments preceding this move */
13502         if (commentList[i] != NULL) {
13503             if (linelen > 0) fprintf(f, "\n");
13504             fprintf(f, "%s", commentList[i]);
13505             linelen = 0;
13506             newblock = TRUE;
13507         }
13508
13509         /* Format move number */
13510         if ((i % 2) == 0)
13511           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13512         else
13513           if (newblock)
13514             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13515           else
13516             numtext[0] = NULLCHAR;
13517
13518         numlen = strlen(numtext);
13519         newblock = FALSE;
13520
13521         /* Print move number */
13522         blank = linelen > 0 && numlen > 0;
13523         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13524             fprintf(f, "\n");
13525             linelen = 0;
13526             blank = 0;
13527         }
13528         if (blank) {
13529             fprintf(f, " ");
13530             linelen++;
13531         }
13532         fprintf(f, "%s", numtext);
13533         linelen += numlen;
13534
13535         /* Get move */
13536         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13537         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13538
13539         /* Print move */
13540         blank = linelen > 0 && movelen > 0;
13541         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13542             fprintf(f, "\n");
13543             linelen = 0;
13544             blank = 0;
13545         }
13546         if (blank) {
13547             fprintf(f, " ");
13548             linelen++;
13549         }
13550         fprintf(f, "%s", move_buffer);
13551         linelen += movelen;
13552
13553         /* [AS] Add PV info if present */
13554         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13555             /* [HGM] add time */
13556             char buf[MSG_SIZ]; int seconds;
13557
13558             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13559
13560             if( seconds <= 0)
13561               buf[0] = 0;
13562             else
13563               if( seconds < 30 )
13564                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13565               else
13566                 {
13567                   seconds = (seconds + 4)/10; // round to full seconds
13568                   if( seconds < 60 )
13569                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13570                   else
13571                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13572                 }
13573
13574             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13575                       pvInfoList[i].score >= 0 ? "+" : "",
13576                       pvInfoList[i].score / 100.0,
13577                       pvInfoList[i].depth,
13578                       buf );
13579
13580             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13581
13582             /* Print score/depth */
13583             blank = linelen > 0 && movelen > 0;
13584             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13585                 fprintf(f, "\n");
13586                 linelen = 0;
13587                 blank = 0;
13588             }
13589             if (blank) {
13590                 fprintf(f, " ");
13591                 linelen++;
13592             }
13593             fprintf(f, "%s", move_buffer);
13594             linelen += movelen;
13595         }
13596
13597         i++;
13598     }
13599
13600     /* Start a new line */
13601     if (linelen > 0) fprintf(f, "\n");
13602
13603     /* Print comments after last move */
13604     if (commentList[i] != NULL) {
13605         fprintf(f, "%s\n", commentList[i]);
13606     }
13607
13608     /* Print result */
13609     if (gameInfo.resultDetails != NULL &&
13610         gameInfo.resultDetails[0] != NULLCHAR) {
13611         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13612         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13613            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13614             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13615         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13616     } else {
13617         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13618     }
13619 }
13620
13621 /* Save game in PGN style and close the file */
13622 int
13623 SaveGamePGN (FILE *f)
13624 {
13625     SaveGamePGN2(f);
13626     fclose(f);
13627     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13628     return TRUE;
13629 }
13630
13631 /* Save game in old style and close the file */
13632 int
13633 SaveGameOldStyle (FILE *f)
13634 {
13635     int i, offset;
13636     time_t tm;
13637
13638     tm = time((time_t *) NULL);
13639
13640     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13641     PrintOpponents(f);
13642
13643     if (backwardMostMove > 0 || startedFromSetupPosition) {
13644         fprintf(f, "\n[--------------\n");
13645         PrintPosition(f, backwardMostMove);
13646         fprintf(f, "--------------]\n");
13647     } else {
13648         fprintf(f, "\n");
13649     }
13650
13651     i = backwardMostMove;
13652     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13653
13654     while (i < forwardMostMove) {
13655         if (commentList[i] != NULL) {
13656             fprintf(f, "[%s]\n", commentList[i]);
13657         }
13658
13659         if ((i % 2) == 1) {
13660             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13661             i++;
13662         } else {
13663             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13664             i++;
13665             if (commentList[i] != NULL) {
13666                 fprintf(f, "\n");
13667                 continue;
13668             }
13669             if (i >= forwardMostMove) {
13670                 fprintf(f, "\n");
13671                 break;
13672             }
13673             fprintf(f, "%s\n", parseList[i]);
13674             i++;
13675         }
13676     }
13677
13678     if (commentList[i] != NULL) {
13679         fprintf(f, "[%s]\n", commentList[i]);
13680     }
13681
13682     /* This isn't really the old style, but it's close enough */
13683     if (gameInfo.resultDetails != NULL &&
13684         gameInfo.resultDetails[0] != NULLCHAR) {
13685         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13686                 gameInfo.resultDetails);
13687     } else {
13688         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13689     }
13690
13691     fclose(f);
13692     return TRUE;
13693 }
13694
13695 /* Save the current game to open file f and close the file */
13696 int
13697 SaveGame (FILE *f, int dummy, char *dummy2)
13698 {
13699     if (gameMode == EditPosition) EditPositionDone(TRUE);
13700     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13701     if (appData.oldSaveStyle)
13702       return SaveGameOldStyle(f);
13703     else
13704       return SaveGamePGN(f);
13705 }
13706
13707 /* Save the current position to the given file */
13708 int
13709 SavePositionToFile (char *filename)
13710 {
13711     FILE *f;
13712     char buf[MSG_SIZ];
13713
13714     if (strcmp(filename, "-") == 0) {
13715         return SavePosition(stdout, 0, NULL);
13716     } else {
13717         f = fopen(filename, "a");
13718         if (f == NULL) {
13719             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13720             DisplayError(buf, errno);
13721             return FALSE;
13722         } else {
13723             safeStrCpy(buf, lastMsg, MSG_SIZ);
13724             DisplayMessage(_("Waiting for access to save file"), "");
13725             flock(fileno(f), LOCK_EX); // [HGM] lock
13726             DisplayMessage(_("Saving position"), "");
13727             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13728             SavePosition(f, 0, NULL);
13729             DisplayMessage(buf, "");
13730             return TRUE;
13731         }
13732     }
13733 }
13734
13735 /* Save the current position to the given open file and close the file */
13736 int
13737 SavePosition (FILE *f, int dummy, char *dummy2)
13738 {
13739     time_t tm;
13740     char *fen;
13741
13742     if (gameMode == EditPosition) EditPositionDone(TRUE);
13743     if (appData.oldSaveStyle) {
13744         tm = time((time_t *) NULL);
13745
13746         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13747         PrintOpponents(f);
13748         fprintf(f, "[--------------\n");
13749         PrintPosition(f, currentMove);
13750         fprintf(f, "--------------]\n");
13751     } else {
13752         fen = PositionToFEN(currentMove, NULL, 1);
13753         fprintf(f, "%s\n", fen);
13754         free(fen);
13755     }
13756     fclose(f);
13757     return TRUE;
13758 }
13759
13760 void
13761 ReloadCmailMsgEvent (int unregister)
13762 {
13763 #if !WIN32
13764     static char *inFilename = NULL;
13765     static char *outFilename;
13766     int i;
13767     struct stat inbuf, outbuf;
13768     int status;
13769
13770     /* Any registered moves are unregistered if unregister is set, */
13771     /* i.e. invoked by the signal handler */
13772     if (unregister) {
13773         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13774             cmailMoveRegistered[i] = FALSE;
13775             if (cmailCommentList[i] != NULL) {
13776                 free(cmailCommentList[i]);
13777                 cmailCommentList[i] = NULL;
13778             }
13779         }
13780         nCmailMovesRegistered = 0;
13781     }
13782
13783     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13784         cmailResult[i] = CMAIL_NOT_RESULT;
13785     }
13786     nCmailResults = 0;
13787
13788     if (inFilename == NULL) {
13789         /* Because the filenames are static they only get malloced once  */
13790         /* and they never get freed                                      */
13791         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13792         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13793
13794         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
13795         sprintf(outFilename, "%s.out", appData.cmailGameName);
13796     }
13797
13798     status = stat(outFilename, &outbuf);
13799     if (status < 0) {
13800         cmailMailedMove = FALSE;
13801     } else {
13802         status = stat(inFilename, &inbuf);
13803         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
13804     }
13805
13806     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
13807        counts the games, notes how each one terminated, etc.
13808
13809        It would be nice to remove this kludge and instead gather all
13810        the information while building the game list.  (And to keep it
13811        in the game list nodes instead of having a bunch of fixed-size
13812        parallel arrays.)  Note this will require getting each game's
13813        termination from the PGN tags, as the game list builder does
13814        not process the game moves.  --mann
13815        */
13816     cmailMsgLoaded = TRUE;
13817     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
13818
13819     /* Load first game in the file or popup game menu */
13820     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
13821
13822 #endif /* !WIN32 */
13823     return;
13824 }
13825
13826 int
13827 RegisterMove ()
13828 {
13829     FILE *f;
13830     char string[MSG_SIZ];
13831
13832     if (   cmailMailedMove
13833         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
13834         return TRUE;            /* Allow free viewing  */
13835     }
13836
13837     /* Unregister move to ensure that we don't leave RegisterMove        */
13838     /* with the move registered when the conditions for registering no   */
13839     /* longer hold                                                       */
13840     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
13841         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
13842         nCmailMovesRegistered --;
13843
13844         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
13845           {
13846               free(cmailCommentList[lastLoadGameNumber - 1]);
13847               cmailCommentList[lastLoadGameNumber - 1] = NULL;
13848           }
13849     }
13850
13851     if (cmailOldMove == -1) {
13852         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
13853         return FALSE;
13854     }
13855
13856     if (currentMove > cmailOldMove + 1) {
13857         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
13858         return FALSE;
13859     }
13860
13861     if (currentMove < cmailOldMove) {
13862         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
13863         return FALSE;
13864     }
13865
13866     if (forwardMostMove > currentMove) {
13867         /* Silently truncate extra moves */
13868         TruncateGame();
13869     }
13870
13871     if (   (currentMove == cmailOldMove + 1)
13872         || (   (currentMove == cmailOldMove)
13873             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
13874                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
13875         if (gameInfo.result != GameUnfinished) {
13876             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
13877         }
13878
13879         if (commentList[currentMove] != NULL) {
13880             cmailCommentList[lastLoadGameNumber - 1]
13881               = StrSave(commentList[currentMove]);
13882         }
13883         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
13884
13885         if (appData.debugMode)
13886           fprintf(debugFP, "Saving %s for game %d\n",
13887                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
13888
13889         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
13890
13891         f = fopen(string, "w");
13892         if (appData.oldSaveStyle) {
13893             SaveGameOldStyle(f); /* also closes the file */
13894
13895             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
13896             f = fopen(string, "w");
13897             SavePosition(f, 0, NULL); /* also closes the file */
13898         } else {
13899             fprintf(f, "{--------------\n");
13900             PrintPosition(f, currentMove);
13901             fprintf(f, "--------------}\n\n");
13902
13903             SaveGame(f, 0, NULL); /* also closes the file*/
13904         }
13905
13906         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
13907         nCmailMovesRegistered ++;
13908     } else if (nCmailGames == 1) {
13909         DisplayError(_("You have not made a move yet"), 0);
13910         return FALSE;
13911     }
13912
13913     return TRUE;
13914 }
13915
13916 void
13917 MailMoveEvent ()
13918 {
13919 #if !WIN32
13920     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
13921     FILE *commandOutput;
13922     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
13923     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
13924     int nBuffers;
13925     int i;
13926     int archived;
13927     char *arcDir;
13928
13929     if (! cmailMsgLoaded) {
13930         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
13931         return;
13932     }
13933
13934     if (nCmailGames == nCmailResults) {
13935         DisplayError(_("No unfinished games"), 0);
13936         return;
13937     }
13938
13939 #if CMAIL_PROHIBIT_REMAIL
13940     if (cmailMailedMove) {
13941       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);
13942         DisplayError(msg, 0);
13943         return;
13944     }
13945 #endif
13946
13947     if (! (cmailMailedMove || RegisterMove())) return;
13948
13949     if (   cmailMailedMove
13950         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
13951       snprintf(string, MSG_SIZ, partCommandString,
13952                appData.debugMode ? " -v" : "", appData.cmailGameName);
13953         commandOutput = popen(string, "r");
13954
13955         if (commandOutput == NULL) {
13956             DisplayError(_("Failed to invoke cmail"), 0);
13957         } else {
13958             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
13959                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
13960             }
13961             if (nBuffers > 1) {
13962                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
13963                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
13964                 nBytes = MSG_SIZ - 1;
13965             } else {
13966                 (void) memcpy(msg, buffer, nBytes);
13967             }
13968             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
13969
13970             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13971                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13972
13973                 archived = TRUE;
13974                 for (i = 0; i < nCmailGames; i ++) {
13975                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13976                         archived = FALSE;
13977                     }
13978                 }
13979                 if (   archived
13980                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13981                         != NULL)) {
13982                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13983                            arcDir,
13984                            appData.cmailGameName,
13985                            gameInfo.date);
13986                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13987                     cmailMsgLoaded = FALSE;
13988                 }
13989             }
13990
13991             DisplayInformation(msg);
13992             pclose(commandOutput);
13993         }
13994     } else {
13995         if ((*cmailMsg) != '\0') {
13996             DisplayInformation(cmailMsg);
13997         }
13998     }
13999
14000     return;
14001 #endif /* !WIN32 */
14002 }
14003
14004 char *
14005 CmailMsg ()
14006 {
14007 #if WIN32
14008     return NULL;
14009 #else
14010     int  prependComma = 0;
14011     char number[5];
14012     char string[MSG_SIZ];       /* Space for game-list */
14013     int  i;
14014
14015     if (!cmailMsgLoaded) return "";
14016
14017     if (cmailMailedMove) {
14018       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14019     } else {
14020         /* Create a list of games left */
14021       snprintf(string, MSG_SIZ, "[");
14022         for (i = 0; i < nCmailGames; i ++) {
14023             if (! (   cmailMoveRegistered[i]
14024                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14025                 if (prependComma) {
14026                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14027                 } else {
14028                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14029                     prependComma = 1;
14030                 }
14031
14032                 strcat(string, number);
14033             }
14034         }
14035         strcat(string, "]");
14036
14037         if (nCmailMovesRegistered + nCmailResults == 0) {
14038             switch (nCmailGames) {
14039               case 1:
14040                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14041                 break;
14042
14043               case 2:
14044                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14045                 break;
14046
14047               default:
14048                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14049                          nCmailGames);
14050                 break;
14051             }
14052         } else {
14053             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14054               case 1:
14055                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14056                          string);
14057                 break;
14058
14059               case 0:
14060                 if (nCmailResults == nCmailGames) {
14061                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14062                 } else {
14063                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14064                 }
14065                 break;
14066
14067               default:
14068                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14069                          string);
14070             }
14071         }
14072     }
14073     return cmailMsg;
14074 #endif /* WIN32 */
14075 }
14076
14077 void
14078 ResetGameEvent ()
14079 {
14080     if (gameMode == Training)
14081       SetTrainingModeOff();
14082
14083     Reset(TRUE, TRUE);
14084     cmailMsgLoaded = FALSE;
14085     if (appData.icsActive) {
14086       SendToICS(ics_prefix);
14087       SendToICS("refresh\n");
14088     }
14089 }
14090
14091 void
14092 ExitEvent (int status)
14093 {
14094     exiting++;
14095     if (exiting > 2) {
14096       /* Give up on clean exit */
14097       exit(status);
14098     }
14099     if (exiting > 1) {
14100       /* Keep trying for clean exit */
14101       return;
14102     }
14103
14104     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14105     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14106
14107     if (telnetISR != NULL) {
14108       RemoveInputSource(telnetISR);
14109     }
14110     if (icsPR != NoProc) {
14111       DestroyChildProcess(icsPR, TRUE);
14112     }
14113
14114     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14115     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14116
14117     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14118     /* make sure this other one finishes before killing it!                  */
14119     if(endingGame) { int count = 0;
14120         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14121         while(endingGame && count++ < 10) DoSleep(1);
14122         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14123     }
14124
14125     /* Kill off chess programs */
14126     if (first.pr != NoProc) {
14127         ExitAnalyzeMode();
14128
14129         DoSleep( appData.delayBeforeQuit );
14130         SendToProgram("quit\n", &first);
14131         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14132     }
14133     if (second.pr != NoProc) {
14134         DoSleep( appData.delayBeforeQuit );
14135         SendToProgram("quit\n", &second);
14136         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14137     }
14138     if (first.isr != NULL) {
14139         RemoveInputSource(first.isr);
14140     }
14141     if (second.isr != NULL) {
14142         RemoveInputSource(second.isr);
14143     }
14144
14145     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14146     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14147
14148     ShutDownFrontEnd();
14149     exit(status);
14150 }
14151
14152 void
14153 PauseEngine (ChessProgramState *cps)
14154 {
14155     SendToProgram("pause\n", cps);
14156     cps->pause = 2;
14157 }
14158
14159 void
14160 UnPauseEngine (ChessProgramState *cps)
14161 {
14162     SendToProgram("resume\n", cps);
14163     cps->pause = 1;
14164 }
14165
14166 void
14167 PauseEvent ()
14168 {
14169     if (appData.debugMode)
14170         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14171     if (pausing) {
14172         pausing = FALSE;
14173         ModeHighlight();
14174         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14175             StartClocks();
14176             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14177                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14178                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14179             }
14180             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14181             HandleMachineMove(stashedInputMove, stalledEngine);
14182             stalledEngine = NULL;
14183             return;
14184         }
14185         if (gameMode == MachinePlaysWhite ||
14186             gameMode == TwoMachinesPlay   ||
14187             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14188             if(first.pause)  UnPauseEngine(&first);
14189             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14190             if(second.pause) UnPauseEngine(&second);
14191             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14192             StartClocks();
14193         } else {
14194             DisplayBothClocks();
14195         }
14196         if (gameMode == PlayFromGameFile) {
14197             if (appData.timeDelay >= 0)
14198                 AutoPlayGameLoop();
14199         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14200             Reset(FALSE, TRUE);
14201             SendToICS(ics_prefix);
14202             SendToICS("refresh\n");
14203         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14204             ForwardInner(forwardMostMove);
14205         }
14206         pauseExamInvalid = FALSE;
14207     } else {
14208         switch (gameMode) {
14209           default:
14210             return;
14211           case IcsExamining:
14212             pauseExamForwardMostMove = forwardMostMove;
14213             pauseExamInvalid = FALSE;
14214             /* fall through */
14215           case IcsObserving:
14216           case IcsPlayingWhite:
14217           case IcsPlayingBlack:
14218             pausing = TRUE;
14219             ModeHighlight();
14220             return;
14221           case PlayFromGameFile:
14222             (void) StopLoadGameTimer();
14223             pausing = TRUE;
14224             ModeHighlight();
14225             break;
14226           case BeginningOfGame:
14227             if (appData.icsActive) return;
14228             /* else fall through */
14229           case MachinePlaysWhite:
14230           case MachinePlaysBlack:
14231           case TwoMachinesPlay:
14232             if (forwardMostMove == 0)
14233               return;           /* don't pause if no one has moved */
14234             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14235                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14236                 if(onMove->pause) {           // thinking engine can be paused
14237                     PauseEngine(onMove);      // do it
14238                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14239                         PauseEngine(onMove->other);
14240                     else
14241                         SendToProgram("easy\n", onMove->other);
14242                     StopClocks();
14243                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14244             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14245                 if(first.pause) {
14246                     PauseEngine(&first);
14247                     StopClocks();
14248                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14249             } else { // human on move, pause pondering by either method
14250                 if(first.pause)
14251                     PauseEngine(&first);
14252                 else if(appData.ponderNextMove)
14253                     SendToProgram("easy\n", &first);
14254                 StopClocks();
14255             }
14256             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14257           case AnalyzeMode:
14258             pausing = TRUE;
14259             ModeHighlight();
14260             break;
14261         }
14262     }
14263 }
14264
14265 void
14266 EditCommentEvent ()
14267 {
14268     char title[MSG_SIZ];
14269
14270     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14271       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14272     } else {
14273       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14274                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14275                parseList[currentMove - 1]);
14276     }
14277
14278     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14279 }
14280
14281
14282 void
14283 EditTagsEvent ()
14284 {
14285     char *tags = PGNTags(&gameInfo);
14286     bookUp = FALSE;
14287     EditTagsPopUp(tags, NULL);
14288     free(tags);
14289 }
14290
14291 void
14292 ToggleSecond ()
14293 {
14294   if(second.analyzing) {
14295     SendToProgram("exit\n", &second);
14296     second.analyzing = FALSE;
14297   } else {
14298     if (second.pr == NoProc) StartChessProgram(&second);
14299     InitChessProgram(&second, FALSE);
14300     FeedMovesToProgram(&second, currentMove);
14301
14302     SendToProgram("analyze\n", &second);
14303     second.analyzing = TRUE;
14304   }
14305 }
14306
14307 /* Toggle ShowThinking */
14308 void
14309 ToggleShowThinking()
14310 {
14311   appData.showThinking = !appData.showThinking;
14312   ShowThinkingEvent();
14313 }
14314
14315 int
14316 AnalyzeModeEvent ()
14317 {
14318     char buf[MSG_SIZ];
14319
14320     if (!first.analysisSupport) {
14321       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14322       DisplayError(buf, 0);
14323       return 0;
14324     }
14325     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14326     if (appData.icsActive) {
14327         if (gameMode != IcsObserving) {
14328           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14329             DisplayError(buf, 0);
14330             /* secure check */
14331             if (appData.icsEngineAnalyze) {
14332                 if (appData.debugMode)
14333                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14334                 ExitAnalyzeMode();
14335                 ModeHighlight();
14336             }
14337             return 0;
14338         }
14339         /* if enable, user wants to disable icsEngineAnalyze */
14340         if (appData.icsEngineAnalyze) {
14341                 ExitAnalyzeMode();
14342                 ModeHighlight();
14343                 return 0;
14344         }
14345         appData.icsEngineAnalyze = TRUE;
14346         if (appData.debugMode)
14347             fprintf(debugFP, "ICS engine analyze starting... \n");
14348     }
14349
14350     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14351     if (appData.noChessProgram || gameMode == AnalyzeMode)
14352       return 0;
14353
14354     if (gameMode != AnalyzeFile) {
14355         if (!appData.icsEngineAnalyze) {
14356                EditGameEvent();
14357                if (gameMode != EditGame) return 0;
14358         }
14359         if (!appData.showThinking) ToggleShowThinking();
14360         ResurrectChessProgram();
14361         SendToProgram("analyze\n", &first);
14362         first.analyzing = TRUE;
14363         /*first.maybeThinking = TRUE;*/
14364         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14365         EngineOutputPopUp();
14366     }
14367     if (!appData.icsEngineAnalyze) {
14368         gameMode = AnalyzeMode;
14369         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14370     }
14371     pausing = FALSE;
14372     ModeHighlight();
14373     SetGameInfo();
14374
14375     StartAnalysisClock();
14376     GetTimeMark(&lastNodeCountTime);
14377     lastNodeCount = 0;
14378     return 1;
14379 }
14380
14381 void
14382 AnalyzeFileEvent ()
14383 {
14384     if (appData.noChessProgram || gameMode == AnalyzeFile)
14385       return;
14386
14387     if (!first.analysisSupport) {
14388       char buf[MSG_SIZ];
14389       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14390       DisplayError(buf, 0);
14391       return;
14392     }
14393
14394     if (gameMode != AnalyzeMode) {
14395         keepInfo = 1; // mere annotating should not alter PGN tags
14396         EditGameEvent();
14397         keepInfo = 0;
14398         if (gameMode != EditGame) return;
14399         if (!appData.showThinking) ToggleShowThinking();
14400         ResurrectChessProgram();
14401         SendToProgram("analyze\n", &first);
14402         first.analyzing = TRUE;
14403         /*first.maybeThinking = TRUE;*/
14404         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14405         EngineOutputPopUp();
14406     }
14407     gameMode = AnalyzeFile;
14408     pausing = FALSE;
14409     ModeHighlight();
14410
14411     StartAnalysisClock();
14412     GetTimeMark(&lastNodeCountTime);
14413     lastNodeCount = 0;
14414     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14415     AnalysisPeriodicEvent(1);
14416 }
14417
14418 void
14419 MachineWhiteEvent ()
14420 {
14421     char buf[MSG_SIZ];
14422     char *bookHit = NULL;
14423
14424     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14425       return;
14426
14427
14428     if (gameMode == PlayFromGameFile ||
14429         gameMode == TwoMachinesPlay  ||
14430         gameMode == Training         ||
14431         gameMode == AnalyzeMode      ||
14432         gameMode == EndOfGame)
14433         EditGameEvent();
14434
14435     if (gameMode == EditPosition)
14436         EditPositionDone(TRUE);
14437
14438     if (!WhiteOnMove(currentMove)) {
14439         DisplayError(_("It is not White's turn"), 0);
14440         return;
14441     }
14442
14443     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14444       ExitAnalyzeMode();
14445
14446     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14447         gameMode == AnalyzeFile)
14448         TruncateGame();
14449
14450     ResurrectChessProgram();    /* in case it isn't running */
14451     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14452         gameMode = MachinePlaysWhite;
14453         ResetClocks();
14454     } else
14455     gameMode = MachinePlaysWhite;
14456     pausing = FALSE;
14457     ModeHighlight();
14458     SetGameInfo();
14459     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14460     DisplayTitle(buf);
14461     if (first.sendName) {
14462       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14463       SendToProgram(buf, &first);
14464     }
14465     if (first.sendTime) {
14466       if (first.useColors) {
14467         SendToProgram("black\n", &first); /*gnu kludge*/
14468       }
14469       SendTimeRemaining(&first, TRUE);
14470     }
14471     if (first.useColors) {
14472       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14473     }
14474     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14475     SetMachineThinkingEnables();
14476     first.maybeThinking = TRUE;
14477     StartClocks();
14478     firstMove = FALSE;
14479
14480     if (appData.autoFlipView && !flipView) {
14481       flipView = !flipView;
14482       DrawPosition(FALSE, NULL);
14483       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14484     }
14485
14486     if(bookHit) { // [HGM] book: simulate book reply
14487         static char bookMove[MSG_SIZ]; // a bit generous?
14488
14489         programStats.nodes = programStats.depth = programStats.time =
14490         programStats.score = programStats.got_only_move = 0;
14491         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14492
14493         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14494         strcat(bookMove, bookHit);
14495         HandleMachineMove(bookMove, &first);
14496     }
14497 }
14498
14499 void
14500 MachineBlackEvent ()
14501 {
14502   char buf[MSG_SIZ];
14503   char *bookHit = NULL;
14504
14505     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14506         return;
14507
14508
14509     if (gameMode == PlayFromGameFile ||
14510         gameMode == TwoMachinesPlay  ||
14511         gameMode == Training         ||
14512         gameMode == AnalyzeMode      ||
14513         gameMode == EndOfGame)
14514         EditGameEvent();
14515
14516     if (gameMode == EditPosition)
14517         EditPositionDone(TRUE);
14518
14519     if (WhiteOnMove(currentMove)) {
14520         DisplayError(_("It is not Black's turn"), 0);
14521         return;
14522     }
14523
14524     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14525       ExitAnalyzeMode();
14526
14527     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14528         gameMode == AnalyzeFile)
14529         TruncateGame();
14530
14531     ResurrectChessProgram();    /* in case it isn't running */
14532     gameMode = MachinePlaysBlack;
14533     pausing = FALSE;
14534     ModeHighlight();
14535     SetGameInfo();
14536     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14537     DisplayTitle(buf);
14538     if (first.sendName) {
14539       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14540       SendToProgram(buf, &first);
14541     }
14542     if (first.sendTime) {
14543       if (first.useColors) {
14544         SendToProgram("white\n", &first); /*gnu kludge*/
14545       }
14546       SendTimeRemaining(&first, FALSE);
14547     }
14548     if (first.useColors) {
14549       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14550     }
14551     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14552     SetMachineThinkingEnables();
14553     first.maybeThinking = TRUE;
14554     StartClocks();
14555
14556     if (appData.autoFlipView && flipView) {
14557       flipView = !flipView;
14558       DrawPosition(FALSE, NULL);
14559       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14560     }
14561     if(bookHit) { // [HGM] book: simulate book reply
14562         static char bookMove[MSG_SIZ]; // a bit generous?
14563
14564         programStats.nodes = programStats.depth = programStats.time =
14565         programStats.score = programStats.got_only_move = 0;
14566         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14567
14568         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14569         strcat(bookMove, bookHit);
14570         HandleMachineMove(bookMove, &first);
14571     }
14572 }
14573
14574
14575 void
14576 DisplayTwoMachinesTitle ()
14577 {
14578     char buf[MSG_SIZ];
14579     if (appData.matchGames > 0) {
14580         if(appData.tourneyFile[0]) {
14581           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14582                    gameInfo.white, _("vs."), gameInfo.black,
14583                    nextGame+1, appData.matchGames+1,
14584                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14585         } else
14586         if (first.twoMachinesColor[0] == 'w') {
14587           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14588                    gameInfo.white, _("vs."),  gameInfo.black,
14589                    first.matchWins, second.matchWins,
14590                    matchGame - 1 - (first.matchWins + second.matchWins));
14591         } else {
14592           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14593                    gameInfo.white, _("vs."), gameInfo.black,
14594                    second.matchWins, first.matchWins,
14595                    matchGame - 1 - (first.matchWins + second.matchWins));
14596         }
14597     } else {
14598       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14599     }
14600     DisplayTitle(buf);
14601 }
14602
14603 void
14604 SettingsMenuIfReady ()
14605 {
14606   if (second.lastPing != second.lastPong) {
14607     DisplayMessage("", _("Waiting for second chess program"));
14608     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14609     return;
14610   }
14611   ThawUI();
14612   DisplayMessage("", "");
14613   SettingsPopUp(&second);
14614 }
14615
14616 int
14617 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14618 {
14619     char buf[MSG_SIZ];
14620     if (cps->pr == NoProc) {
14621         StartChessProgram(cps);
14622         if (cps->protocolVersion == 1) {
14623           retry();
14624           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14625         } else {
14626           /* kludge: allow timeout for initial "feature" command */
14627           if(retry != TwoMachinesEventIfReady) FreezeUI();
14628           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14629           DisplayMessage("", buf);
14630           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14631         }
14632         return 1;
14633     }
14634     return 0;
14635 }
14636
14637 void
14638 TwoMachinesEvent P((void))
14639 {
14640     int i;
14641     char buf[MSG_SIZ];
14642     ChessProgramState *onmove;
14643     char *bookHit = NULL;
14644     static int stalling = 0;
14645     TimeMark now;
14646     long wait;
14647
14648     if (appData.noChessProgram) return;
14649
14650     switch (gameMode) {
14651       case TwoMachinesPlay:
14652         return;
14653       case MachinePlaysWhite:
14654       case MachinePlaysBlack:
14655         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14656             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14657             return;
14658         }
14659         /* fall through */
14660       case BeginningOfGame:
14661       case PlayFromGameFile:
14662       case EndOfGame:
14663         EditGameEvent();
14664         if (gameMode != EditGame) return;
14665         break;
14666       case EditPosition:
14667         EditPositionDone(TRUE);
14668         break;
14669       case AnalyzeMode:
14670       case AnalyzeFile:
14671         ExitAnalyzeMode();
14672         break;
14673       case EditGame:
14674       default:
14675         break;
14676     }
14677
14678 //    forwardMostMove = currentMove;
14679     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14680     startingEngine = TRUE;
14681
14682     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14683
14684     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14685     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14686       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14687       return;
14688     }
14689     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14690
14691     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14692                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14693         startingEngine = matchMode = FALSE;
14694         DisplayError("second engine does not play this", 0);
14695         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14696         EditGameEvent(); // switch back to EditGame mode
14697         return;
14698     }
14699
14700     if(!stalling) {
14701       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14702       SendToProgram("force\n", &second);
14703       stalling = 1;
14704       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14705       return;
14706     }
14707     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14708     if(appData.matchPause>10000 || appData.matchPause<10)
14709                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14710     wait = SubtractTimeMarks(&now, &pauseStart);
14711     if(wait < appData.matchPause) {
14712         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14713         return;
14714     }
14715     // we are now committed to starting the game
14716     stalling = 0;
14717     DisplayMessage("", "");
14718     if (startedFromSetupPosition) {
14719         SendBoard(&second, backwardMostMove);
14720     if (appData.debugMode) {
14721         fprintf(debugFP, "Two Machines\n");
14722     }
14723     }
14724     for (i = backwardMostMove; i < forwardMostMove; i++) {
14725         SendMoveToProgram(i, &second);
14726     }
14727
14728     gameMode = TwoMachinesPlay;
14729     pausing = startingEngine = FALSE;
14730     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14731     SetGameInfo();
14732     DisplayTwoMachinesTitle();
14733     firstMove = TRUE;
14734     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14735         onmove = &first;
14736     } else {
14737         onmove = &second;
14738     }
14739     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14740     SendToProgram(first.computerString, &first);
14741     if (first.sendName) {
14742       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14743       SendToProgram(buf, &first);
14744     }
14745     SendToProgram(second.computerString, &second);
14746     if (second.sendName) {
14747       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14748       SendToProgram(buf, &second);
14749     }
14750
14751     ResetClocks();
14752     if (!first.sendTime || !second.sendTime) {
14753         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14754         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14755     }
14756     if (onmove->sendTime) {
14757       if (onmove->useColors) {
14758         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14759       }
14760       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14761     }
14762     if (onmove->useColors) {
14763       SendToProgram(onmove->twoMachinesColor, onmove);
14764     }
14765     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14766 //    SendToProgram("go\n", onmove);
14767     onmove->maybeThinking = TRUE;
14768     SetMachineThinkingEnables();
14769
14770     StartClocks();
14771
14772     if(bookHit) { // [HGM] book: simulate book reply
14773         static char bookMove[MSG_SIZ]; // a bit generous?
14774
14775         programStats.nodes = programStats.depth = programStats.time =
14776         programStats.score = programStats.got_only_move = 0;
14777         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14778
14779         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14780         strcat(bookMove, bookHit);
14781         savedMessage = bookMove; // args for deferred call
14782         savedState = onmove;
14783         ScheduleDelayedEvent(DeferredBookMove, 1);
14784     }
14785 }
14786
14787 void
14788 TrainingEvent ()
14789 {
14790     if (gameMode == Training) {
14791       SetTrainingModeOff();
14792       gameMode = PlayFromGameFile;
14793       DisplayMessage("", _("Training mode off"));
14794     } else {
14795       gameMode = Training;
14796       animateTraining = appData.animate;
14797
14798       /* make sure we are not already at the end of the game */
14799       if (currentMove < forwardMostMove) {
14800         SetTrainingModeOn();
14801         DisplayMessage("", _("Training mode on"));
14802       } else {
14803         gameMode = PlayFromGameFile;
14804         DisplayError(_("Already at end of game"), 0);
14805       }
14806     }
14807     ModeHighlight();
14808 }
14809
14810 void
14811 IcsClientEvent ()
14812 {
14813     if (!appData.icsActive) return;
14814     switch (gameMode) {
14815       case IcsPlayingWhite:
14816       case IcsPlayingBlack:
14817       case IcsObserving:
14818       case IcsIdle:
14819       case BeginningOfGame:
14820       case IcsExamining:
14821         return;
14822
14823       case EditGame:
14824         break;
14825
14826       case EditPosition:
14827         EditPositionDone(TRUE);
14828         break;
14829
14830       case AnalyzeMode:
14831       case AnalyzeFile:
14832         ExitAnalyzeMode();
14833         break;
14834
14835       default:
14836         EditGameEvent();
14837         break;
14838     }
14839
14840     gameMode = IcsIdle;
14841     ModeHighlight();
14842     return;
14843 }
14844
14845 void
14846 EditGameEvent ()
14847 {
14848     int i;
14849
14850     switch (gameMode) {
14851       case Training:
14852         SetTrainingModeOff();
14853         break;
14854       case MachinePlaysWhite:
14855       case MachinePlaysBlack:
14856       case BeginningOfGame:
14857         SendToProgram("force\n", &first);
14858         SetUserThinkingEnables();
14859         break;
14860       case PlayFromGameFile:
14861         (void) StopLoadGameTimer();
14862         if (gameFileFP != NULL) {
14863             gameFileFP = NULL;
14864         }
14865         break;
14866       case EditPosition:
14867         EditPositionDone(TRUE);
14868         break;
14869       case AnalyzeMode:
14870       case AnalyzeFile:
14871         ExitAnalyzeMode();
14872         SendToProgram("force\n", &first);
14873         break;
14874       case TwoMachinesPlay:
14875         GameEnds(EndOfFile, NULL, GE_PLAYER);
14876         ResurrectChessProgram();
14877         SetUserThinkingEnables();
14878         break;
14879       case EndOfGame:
14880         ResurrectChessProgram();
14881         break;
14882       case IcsPlayingBlack:
14883       case IcsPlayingWhite:
14884         DisplayError(_("Warning: You are still playing a game"), 0);
14885         break;
14886       case IcsObserving:
14887         DisplayError(_("Warning: You are still observing a game"), 0);
14888         break;
14889       case IcsExamining:
14890         DisplayError(_("Warning: You are still examining a game"), 0);
14891         break;
14892       case IcsIdle:
14893         break;
14894       case EditGame:
14895       default:
14896         return;
14897     }
14898
14899     pausing = FALSE;
14900     StopClocks();
14901     first.offeredDraw = second.offeredDraw = 0;
14902
14903     if (gameMode == PlayFromGameFile) {
14904         whiteTimeRemaining = timeRemaining[0][currentMove];
14905         blackTimeRemaining = timeRemaining[1][currentMove];
14906         DisplayTitle("");
14907     }
14908
14909     if (gameMode == MachinePlaysWhite ||
14910         gameMode == MachinePlaysBlack ||
14911         gameMode == TwoMachinesPlay ||
14912         gameMode == EndOfGame) {
14913         i = forwardMostMove;
14914         while (i > currentMove) {
14915             SendToProgram("undo\n", &first);
14916             i--;
14917         }
14918         if(!adjustedClock) {
14919         whiteTimeRemaining = timeRemaining[0][currentMove];
14920         blackTimeRemaining = timeRemaining[1][currentMove];
14921         DisplayBothClocks();
14922         }
14923         if (whiteFlag || blackFlag) {
14924             whiteFlag = blackFlag = 0;
14925         }
14926         DisplayTitle("");
14927     }
14928
14929     gameMode = EditGame;
14930     ModeHighlight();
14931     SetGameInfo();
14932 }
14933
14934
14935 void
14936 EditPositionEvent ()
14937 {
14938     if (gameMode == EditPosition) {
14939         EditGameEvent();
14940         return;
14941     }
14942
14943     EditGameEvent();
14944     if (gameMode != EditGame) return;
14945
14946     gameMode = EditPosition;
14947     ModeHighlight();
14948     SetGameInfo();
14949     if (currentMove > 0)
14950       CopyBoard(boards[0], boards[currentMove]);
14951
14952     blackPlaysFirst = !WhiteOnMove(currentMove);
14953     ResetClocks();
14954     currentMove = forwardMostMove = backwardMostMove = 0;
14955     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
14956     DisplayMove(-1);
14957     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
14958 }
14959
14960 void
14961 ExitAnalyzeMode ()
14962 {
14963     /* [DM] icsEngineAnalyze - possible call from other functions */
14964     if (appData.icsEngineAnalyze) {
14965         appData.icsEngineAnalyze = FALSE;
14966
14967         DisplayMessage("",_("Close ICS engine analyze..."));
14968     }
14969     if (first.analysisSupport && first.analyzing) {
14970       SendToBoth("exit\n");
14971       first.analyzing = second.analyzing = FALSE;
14972     }
14973     thinkOutput[0] = NULLCHAR;
14974 }
14975
14976 void
14977 EditPositionDone (Boolean fakeRights)
14978 {
14979     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
14980
14981     startedFromSetupPosition = TRUE;
14982     InitChessProgram(&first, FALSE);
14983     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
14984       boards[0][EP_STATUS] = EP_NONE;
14985       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
14986       if(boards[0][0][BOARD_WIDTH>>1] == king) {
14987         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
14988         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
14989       } else boards[0][CASTLING][2] = NoRights;
14990       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
14991         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
14992         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
14993       } else boards[0][CASTLING][5] = NoRights;
14994       if(gameInfo.variant == VariantSChess) {
14995         int i;
14996         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
14997           boards[0][VIRGIN][i] = 0;
14998           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
14999           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15000         }
15001       }
15002     }
15003     SendToProgram("force\n", &first);
15004     if (blackPlaysFirst) {
15005         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15006         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15007         currentMove = forwardMostMove = backwardMostMove = 1;
15008         CopyBoard(boards[1], boards[0]);
15009     } else {
15010         currentMove = forwardMostMove = backwardMostMove = 0;
15011     }
15012     SendBoard(&first, forwardMostMove);
15013     if (appData.debugMode) {
15014         fprintf(debugFP, "EditPosDone\n");
15015     }
15016     DisplayTitle("");
15017     DisplayMessage("", "");
15018     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15019     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15020     gameMode = EditGame;
15021     ModeHighlight();
15022     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15023     ClearHighlights(); /* [AS] */
15024 }
15025
15026 /* Pause for `ms' milliseconds */
15027 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15028 void
15029 TimeDelay (long ms)
15030 {
15031     TimeMark m1, m2;
15032
15033     GetTimeMark(&m1);
15034     do {
15035         GetTimeMark(&m2);
15036     } while (SubtractTimeMarks(&m2, &m1) < ms);
15037 }
15038
15039 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15040 void
15041 SendMultiLineToICS (char *buf)
15042 {
15043     char temp[MSG_SIZ+1], *p;
15044     int len;
15045
15046     len = strlen(buf);
15047     if (len > MSG_SIZ)
15048       len = MSG_SIZ;
15049
15050     strncpy(temp, buf, len);
15051     temp[len] = 0;
15052
15053     p = temp;
15054     while (*p) {
15055         if (*p == '\n' || *p == '\r')
15056           *p = ' ';
15057         ++p;
15058     }
15059
15060     strcat(temp, "\n");
15061     SendToICS(temp);
15062     SendToPlayer(temp, strlen(temp));
15063 }
15064
15065 void
15066 SetWhiteToPlayEvent ()
15067 {
15068     if (gameMode == EditPosition) {
15069         blackPlaysFirst = FALSE;
15070         DisplayBothClocks();    /* works because currentMove is 0 */
15071     } else if (gameMode == IcsExamining) {
15072         SendToICS(ics_prefix);
15073         SendToICS("tomove white\n");
15074     }
15075 }
15076
15077 void
15078 SetBlackToPlayEvent ()
15079 {
15080     if (gameMode == EditPosition) {
15081         blackPlaysFirst = TRUE;
15082         currentMove = 1;        /* kludge */
15083         DisplayBothClocks();
15084         currentMove = 0;
15085     } else if (gameMode == IcsExamining) {
15086         SendToICS(ics_prefix);
15087         SendToICS("tomove black\n");
15088     }
15089 }
15090
15091 void
15092 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15093 {
15094     char buf[MSG_SIZ];
15095     ChessSquare piece = boards[0][y][x];
15096     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15097     static int lastVariant;
15098
15099     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15100
15101     switch (selection) {
15102       case ClearBoard:
15103         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15104         MarkTargetSquares(1);
15105         CopyBoard(currentBoard, boards[0]);
15106         CopyBoard(menuBoard, initialPosition);
15107         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15108             SendToICS(ics_prefix);
15109             SendToICS("bsetup clear\n");
15110         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15111             SendToICS(ics_prefix);
15112             SendToICS("clearboard\n");
15113         } else {
15114             int nonEmpty = 0;
15115             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15116                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15117                 for (y = 0; y < BOARD_HEIGHT; y++) {
15118                     if (gameMode == IcsExamining) {
15119                         if (boards[currentMove][y][x] != EmptySquare) {
15120                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15121                                     AAA + x, ONE + y);
15122                             SendToICS(buf);
15123                         }
15124                     } else {
15125                         if(boards[0][y][x] != p) nonEmpty++;
15126                         boards[0][y][x] = p;
15127                     }
15128                 }
15129             }
15130             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15131                 int r;
15132                 for(r = 0; r < BOARD_HEIGHT; r++) {
15133                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15134                     ChessSquare p = menuBoard[r][x];
15135                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15136                   }
15137                 }
15138                 DisplayMessage("Clicking clock again restores position", "");
15139                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15140                 if(!nonEmpty) { // asked to clear an empty board
15141                     CopyBoard(boards[0], menuBoard);
15142                 } else
15143                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15144                     CopyBoard(boards[0], initialPosition);
15145                 } else
15146                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15147                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15148                     CopyBoard(boards[0], erasedBoard);
15149                 } else
15150                     CopyBoard(erasedBoard, currentBoard);
15151
15152             }
15153         }
15154         if (gameMode == EditPosition) {
15155             DrawPosition(FALSE, boards[0]);
15156         }
15157         break;
15158
15159       case WhitePlay:
15160         SetWhiteToPlayEvent();
15161         break;
15162
15163       case BlackPlay:
15164         SetBlackToPlayEvent();
15165         break;
15166
15167       case EmptySquare:
15168         if (gameMode == IcsExamining) {
15169             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15170             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15171             SendToICS(buf);
15172         } else {
15173             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15174                 if(x == BOARD_LEFT-2) {
15175                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15176                     boards[0][y][1] = 0;
15177                 } else
15178                 if(x == BOARD_RGHT+1) {
15179                     if(y >= gameInfo.holdingsSize) break;
15180                     boards[0][y][BOARD_WIDTH-2] = 0;
15181                 } else break;
15182             }
15183             boards[0][y][x] = EmptySquare;
15184             DrawPosition(FALSE, boards[0]);
15185         }
15186         break;
15187
15188       case PromotePiece:
15189         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15190            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15191             selection = (ChessSquare) (PROMOTED piece);
15192         } else if(piece == EmptySquare) selection = WhiteSilver;
15193         else selection = (ChessSquare)((int)piece - 1);
15194         goto defaultlabel;
15195
15196       case DemotePiece:
15197         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15198            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15199             selection = (ChessSquare) (DEMOTED piece);
15200         } else if(piece == EmptySquare) selection = BlackSilver;
15201         else selection = (ChessSquare)((int)piece + 1);
15202         goto defaultlabel;
15203
15204       case WhiteQueen:
15205       case BlackQueen:
15206         if(gameInfo.variant == VariantShatranj ||
15207            gameInfo.variant == VariantXiangqi  ||
15208            gameInfo.variant == VariantCourier  ||
15209            gameInfo.variant == VariantASEAN    ||
15210            gameInfo.variant == VariantMakruk     )
15211             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15212         goto defaultlabel;
15213
15214       case WhiteKing:
15215       case BlackKing:
15216         if(gameInfo.variant == VariantXiangqi)
15217             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15218         if(gameInfo.variant == VariantKnightmate)
15219             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15220       default:
15221         defaultlabel:
15222         if (gameMode == IcsExamining) {
15223             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15224             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15225                      PieceToChar(selection), AAA + x, ONE + y);
15226             SendToICS(buf);
15227         } else {
15228             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15229                 int n;
15230                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15231                     n = PieceToNumber(selection - BlackPawn);
15232                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15233                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15234                     boards[0][BOARD_HEIGHT-1-n][1]++;
15235                 } else
15236                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15237                     n = PieceToNumber(selection);
15238                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15239                     boards[0][n][BOARD_WIDTH-1] = selection;
15240                     boards[0][n][BOARD_WIDTH-2]++;
15241                 }
15242             } else
15243             boards[0][y][x] = selection;
15244             DrawPosition(TRUE, boards[0]);
15245             ClearHighlights();
15246             fromX = fromY = -1;
15247         }
15248         break;
15249     }
15250 }
15251
15252
15253 void
15254 DropMenuEvent (ChessSquare selection, int x, int y)
15255 {
15256     ChessMove moveType;
15257
15258     switch (gameMode) {
15259       case IcsPlayingWhite:
15260       case MachinePlaysBlack:
15261         if (!WhiteOnMove(currentMove)) {
15262             DisplayMoveError(_("It is Black's turn"));
15263             return;
15264         }
15265         moveType = WhiteDrop;
15266         break;
15267       case IcsPlayingBlack:
15268       case MachinePlaysWhite:
15269         if (WhiteOnMove(currentMove)) {
15270             DisplayMoveError(_("It is White's turn"));
15271             return;
15272         }
15273         moveType = BlackDrop;
15274         break;
15275       case EditGame:
15276         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15277         break;
15278       default:
15279         return;
15280     }
15281
15282     if (moveType == BlackDrop && selection < BlackPawn) {
15283       selection = (ChessSquare) ((int) selection
15284                                  + (int) BlackPawn - (int) WhitePawn);
15285     }
15286     if (boards[currentMove][y][x] != EmptySquare) {
15287         DisplayMoveError(_("That square is occupied"));
15288         return;
15289     }
15290
15291     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15292 }
15293
15294 void
15295 AcceptEvent ()
15296 {
15297     /* Accept a pending offer of any kind from opponent */
15298
15299     if (appData.icsActive) {
15300         SendToICS(ics_prefix);
15301         SendToICS("accept\n");
15302     } else if (cmailMsgLoaded) {
15303         if (currentMove == cmailOldMove &&
15304             commentList[cmailOldMove] != NULL &&
15305             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15306                    "Black offers a draw" : "White offers a draw")) {
15307             TruncateGame();
15308             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15309             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15310         } else {
15311             DisplayError(_("There is no pending offer on this move"), 0);
15312             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15313         }
15314     } else {
15315         /* Not used for offers from chess program */
15316     }
15317 }
15318
15319 void
15320 DeclineEvent ()
15321 {
15322     /* Decline a pending offer of any kind from opponent */
15323
15324     if (appData.icsActive) {
15325         SendToICS(ics_prefix);
15326         SendToICS("decline\n");
15327     } else if (cmailMsgLoaded) {
15328         if (currentMove == cmailOldMove &&
15329             commentList[cmailOldMove] != NULL &&
15330             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15331                    "Black offers a draw" : "White offers a draw")) {
15332 #ifdef NOTDEF
15333             AppendComment(cmailOldMove, "Draw declined", TRUE);
15334             DisplayComment(cmailOldMove - 1, "Draw declined");
15335 #endif /*NOTDEF*/
15336         } else {
15337             DisplayError(_("There is no pending offer on this move"), 0);
15338         }
15339     } else {
15340         /* Not used for offers from chess program */
15341     }
15342 }
15343
15344 void
15345 RematchEvent ()
15346 {
15347     /* Issue ICS rematch command */
15348     if (appData.icsActive) {
15349         SendToICS(ics_prefix);
15350         SendToICS("rematch\n");
15351     }
15352 }
15353
15354 void
15355 CallFlagEvent ()
15356 {
15357     /* Call your opponent's flag (claim a win on time) */
15358     if (appData.icsActive) {
15359         SendToICS(ics_prefix);
15360         SendToICS("flag\n");
15361     } else {
15362         switch (gameMode) {
15363           default:
15364             return;
15365           case MachinePlaysWhite:
15366             if (whiteFlag) {
15367                 if (blackFlag)
15368                   GameEnds(GameIsDrawn, "Both players ran out of time",
15369                            GE_PLAYER);
15370                 else
15371                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15372             } else {
15373                 DisplayError(_("Your opponent is not out of time"), 0);
15374             }
15375             break;
15376           case MachinePlaysBlack:
15377             if (blackFlag) {
15378                 if (whiteFlag)
15379                   GameEnds(GameIsDrawn, "Both players ran out of time",
15380                            GE_PLAYER);
15381                 else
15382                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15383             } else {
15384                 DisplayError(_("Your opponent is not out of time"), 0);
15385             }
15386             break;
15387         }
15388     }
15389 }
15390
15391 void
15392 ClockClick (int which)
15393 {       // [HGM] code moved to back-end from winboard.c
15394         if(which) { // black clock
15395           if (gameMode == EditPosition || gameMode == IcsExamining) {
15396             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15397             SetBlackToPlayEvent();
15398           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15399                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15400           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15401           } else if (shiftKey) {
15402             AdjustClock(which, -1);
15403           } else if (gameMode == IcsPlayingWhite ||
15404                      gameMode == MachinePlaysBlack) {
15405             CallFlagEvent();
15406           }
15407         } else { // white clock
15408           if (gameMode == EditPosition || gameMode == IcsExamining) {
15409             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15410             SetWhiteToPlayEvent();
15411           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15412                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15413           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15414           } else if (shiftKey) {
15415             AdjustClock(which, -1);
15416           } else if (gameMode == IcsPlayingBlack ||
15417                    gameMode == MachinePlaysWhite) {
15418             CallFlagEvent();
15419           }
15420         }
15421 }
15422
15423 void
15424 DrawEvent ()
15425 {
15426     /* Offer draw or accept pending draw offer from opponent */
15427
15428     if (appData.icsActive) {
15429         /* Note: tournament rules require draw offers to be
15430            made after you make your move but before you punch
15431            your clock.  Currently ICS doesn't let you do that;
15432            instead, you immediately punch your clock after making
15433            a move, but you can offer a draw at any time. */
15434
15435         SendToICS(ics_prefix);
15436         SendToICS("draw\n");
15437         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15438     } else if (cmailMsgLoaded) {
15439         if (currentMove == cmailOldMove &&
15440             commentList[cmailOldMove] != NULL &&
15441             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15442                    "Black offers a draw" : "White offers a draw")) {
15443             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15444             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15445         } else if (currentMove == cmailOldMove + 1) {
15446             char *offer = WhiteOnMove(cmailOldMove) ?
15447               "White offers a draw" : "Black offers a draw";
15448             AppendComment(currentMove, offer, TRUE);
15449             DisplayComment(currentMove - 1, offer);
15450             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15451         } else {
15452             DisplayError(_("You must make your move before offering a draw"), 0);
15453             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15454         }
15455     } else if (first.offeredDraw) {
15456         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15457     } else {
15458         if (first.sendDrawOffers) {
15459             SendToProgram("draw\n", &first);
15460             userOfferedDraw = TRUE;
15461         }
15462     }
15463 }
15464
15465 void
15466 AdjournEvent ()
15467 {
15468     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15469
15470     if (appData.icsActive) {
15471         SendToICS(ics_prefix);
15472         SendToICS("adjourn\n");
15473     } else {
15474         /* Currently GNU Chess doesn't offer or accept Adjourns */
15475     }
15476 }
15477
15478
15479 void
15480 AbortEvent ()
15481 {
15482     /* Offer Abort or accept pending Abort offer from opponent */
15483
15484     if (appData.icsActive) {
15485         SendToICS(ics_prefix);
15486         SendToICS("abort\n");
15487     } else {
15488         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15489     }
15490 }
15491
15492 void
15493 ResignEvent ()
15494 {
15495     /* Resign.  You can do this even if it's not your turn. */
15496
15497     if (appData.icsActive) {
15498         SendToICS(ics_prefix);
15499         SendToICS("resign\n");
15500     } else {
15501         switch (gameMode) {
15502           case MachinePlaysWhite:
15503             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15504             break;
15505           case MachinePlaysBlack:
15506             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15507             break;
15508           case EditGame:
15509             if (cmailMsgLoaded) {
15510                 TruncateGame();
15511                 if (WhiteOnMove(cmailOldMove)) {
15512                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15513                 } else {
15514                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15515                 }
15516                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15517             }
15518             break;
15519           default:
15520             break;
15521         }
15522     }
15523 }
15524
15525
15526 void
15527 StopObservingEvent ()
15528 {
15529     /* Stop observing current games */
15530     SendToICS(ics_prefix);
15531     SendToICS("unobserve\n");
15532 }
15533
15534 void
15535 StopExaminingEvent ()
15536 {
15537     /* Stop observing current game */
15538     SendToICS(ics_prefix);
15539     SendToICS("unexamine\n");
15540 }
15541
15542 void
15543 ForwardInner (int target)
15544 {
15545     int limit; int oldSeekGraphUp = seekGraphUp;
15546
15547     if (appData.debugMode)
15548         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15549                 target, currentMove, forwardMostMove);
15550
15551     if (gameMode == EditPosition)
15552       return;
15553
15554     seekGraphUp = FALSE;
15555     MarkTargetSquares(1);
15556     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15557
15558     if (gameMode == PlayFromGameFile && !pausing)
15559       PauseEvent();
15560
15561     if (gameMode == IcsExamining && pausing)
15562       limit = pauseExamForwardMostMove;
15563     else
15564       limit = forwardMostMove;
15565
15566     if (target > limit) target = limit;
15567
15568     if (target > 0 && moveList[target - 1][0]) {
15569         int fromX, fromY, toX, toY;
15570         toX = moveList[target - 1][2] - AAA;
15571         toY = moveList[target - 1][3] - ONE;
15572         if (moveList[target - 1][1] == '@') {
15573             if (appData.highlightLastMove) {
15574                 SetHighlights(-1, -1, toX, toY);
15575             }
15576         } else {
15577             int viaX = moveList[target - 1][5] - AAA;
15578             int viaY = moveList[target - 1][6] - ONE;
15579             fromX = moveList[target - 1][0] - AAA;
15580             fromY = moveList[target - 1][1] - ONE;
15581             if (target == currentMove + 1) {
15582                 if(moveList[target - 1][4] == ';') { // multi-leg
15583                     ChessSquare piece = boards[currentMove][viaY][viaX];
15584                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15585                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15586                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15587                     boards[currentMove][viaY][viaX] = piece;
15588                 } else
15589                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15590             }
15591             if (appData.highlightLastMove) {
15592                 SetHighlights(fromX, fromY, toX, toY);
15593             }
15594         }
15595     }
15596     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15597         gameMode == Training || gameMode == PlayFromGameFile ||
15598         gameMode == AnalyzeFile) {
15599         while (currentMove < target) {
15600             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15601             SendMoveToProgram(currentMove++, &first);
15602         }
15603     } else {
15604         currentMove = target;
15605     }
15606
15607     if (gameMode == EditGame || gameMode == EndOfGame) {
15608         whiteTimeRemaining = timeRemaining[0][currentMove];
15609         blackTimeRemaining = timeRemaining[1][currentMove];
15610     }
15611     DisplayBothClocks();
15612     DisplayMove(currentMove - 1);
15613     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15614     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15615     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15616         DisplayComment(currentMove - 1, commentList[currentMove]);
15617     }
15618     ClearMap(); // [HGM] exclude: invalidate map
15619 }
15620
15621
15622 void
15623 ForwardEvent ()
15624 {
15625     if (gameMode == IcsExamining && !pausing) {
15626         SendToICS(ics_prefix);
15627         SendToICS("forward\n");
15628     } else {
15629         ForwardInner(currentMove + 1);
15630     }
15631 }
15632
15633 void
15634 ToEndEvent ()
15635 {
15636     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15637         /* to optimze, we temporarily turn off analysis mode while we feed
15638          * the remaining moves to the engine. Otherwise we get analysis output
15639          * after each move.
15640          */
15641         if (first.analysisSupport) {
15642           SendToProgram("exit\nforce\n", &first);
15643           first.analyzing = FALSE;
15644         }
15645     }
15646
15647     if (gameMode == IcsExamining && !pausing) {
15648         SendToICS(ics_prefix);
15649         SendToICS("forward 999999\n");
15650     } else {
15651         ForwardInner(forwardMostMove);
15652     }
15653
15654     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15655         /* we have fed all the moves, so reactivate analysis mode */
15656         SendToProgram("analyze\n", &first);
15657         first.analyzing = TRUE;
15658         /*first.maybeThinking = TRUE;*/
15659         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15660     }
15661 }
15662
15663 void
15664 BackwardInner (int target)
15665 {
15666     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15667
15668     if (appData.debugMode)
15669         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15670                 target, currentMove, forwardMostMove);
15671
15672     if (gameMode == EditPosition) return;
15673     seekGraphUp = FALSE;
15674     MarkTargetSquares(1);
15675     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15676     if (currentMove <= backwardMostMove) {
15677         ClearHighlights();
15678         DrawPosition(full_redraw, boards[currentMove]);
15679         return;
15680     }
15681     if (gameMode == PlayFromGameFile && !pausing)
15682       PauseEvent();
15683
15684     if (moveList[target][0]) {
15685         int fromX, fromY, toX, toY;
15686         toX = moveList[target][2] - AAA;
15687         toY = moveList[target][3] - ONE;
15688         if (moveList[target][1] == '@') {
15689             if (appData.highlightLastMove) {
15690                 SetHighlights(-1, -1, toX, toY);
15691             }
15692         } else {
15693             fromX = moveList[target][0] - AAA;
15694             fromY = moveList[target][1] - ONE;
15695             if (target == currentMove - 1) {
15696                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15697             }
15698             if (appData.highlightLastMove) {
15699                 SetHighlights(fromX, fromY, toX, toY);
15700             }
15701         }
15702     }
15703     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15704         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15705         while (currentMove > target) {
15706             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15707                 // null move cannot be undone. Reload program with move history before it.
15708                 int i;
15709                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15710                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15711                 }
15712                 SendBoard(&first, i);
15713               if(second.analyzing) SendBoard(&second, i);
15714                 for(currentMove=i; currentMove<target; currentMove++) {
15715                     SendMoveToProgram(currentMove, &first);
15716                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15717                 }
15718                 break;
15719             }
15720             SendToBoth("undo\n");
15721             currentMove--;
15722         }
15723     } else {
15724         currentMove = target;
15725     }
15726
15727     if (gameMode == EditGame || gameMode == EndOfGame) {
15728         whiteTimeRemaining = timeRemaining[0][currentMove];
15729         blackTimeRemaining = timeRemaining[1][currentMove];
15730     }
15731     DisplayBothClocks();
15732     DisplayMove(currentMove - 1);
15733     DrawPosition(full_redraw, boards[currentMove]);
15734     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15735     // [HGM] PV info: routine tests if comment empty
15736     DisplayComment(currentMove - 1, commentList[currentMove]);
15737     ClearMap(); // [HGM] exclude: invalidate map
15738 }
15739
15740 void
15741 BackwardEvent ()
15742 {
15743     if (gameMode == IcsExamining && !pausing) {
15744         SendToICS(ics_prefix);
15745         SendToICS("backward\n");
15746     } else {
15747         BackwardInner(currentMove - 1);
15748     }
15749 }
15750
15751 void
15752 ToStartEvent ()
15753 {
15754     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15755         /* to optimize, we temporarily turn off analysis mode while we undo
15756          * all the moves. Otherwise we get analysis output after each undo.
15757          */
15758         if (first.analysisSupport) {
15759           SendToProgram("exit\nforce\n", &first);
15760           first.analyzing = FALSE;
15761         }
15762     }
15763
15764     if (gameMode == IcsExamining && !pausing) {
15765         SendToICS(ics_prefix);
15766         SendToICS("backward 999999\n");
15767     } else {
15768         BackwardInner(backwardMostMove);
15769     }
15770
15771     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15772         /* we have fed all the moves, so reactivate analysis mode */
15773         SendToProgram("analyze\n", &first);
15774         first.analyzing = TRUE;
15775         /*first.maybeThinking = TRUE;*/
15776         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15777     }
15778 }
15779
15780 void
15781 ToNrEvent (int to)
15782 {
15783   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
15784   if (to >= forwardMostMove) to = forwardMostMove;
15785   if (to <= backwardMostMove) to = backwardMostMove;
15786   if (to < currentMove) {
15787     BackwardInner(to);
15788   } else {
15789     ForwardInner(to);
15790   }
15791 }
15792
15793 void
15794 RevertEvent (Boolean annotate)
15795 {
15796     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
15797         return;
15798     }
15799     if (gameMode != IcsExamining) {
15800         DisplayError(_("You are not examining a game"), 0);
15801         return;
15802     }
15803     if (pausing) {
15804         DisplayError(_("You can't revert while pausing"), 0);
15805         return;
15806     }
15807     SendToICS(ics_prefix);
15808     SendToICS("revert\n");
15809 }
15810
15811 void
15812 RetractMoveEvent ()
15813 {
15814     switch (gameMode) {
15815       case MachinePlaysWhite:
15816       case MachinePlaysBlack:
15817         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
15818             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
15819             return;
15820         }
15821         if (forwardMostMove < 2) return;
15822         currentMove = forwardMostMove = forwardMostMove - 2;
15823         whiteTimeRemaining = timeRemaining[0][currentMove];
15824         blackTimeRemaining = timeRemaining[1][currentMove];
15825         DisplayBothClocks();
15826         DisplayMove(currentMove - 1);
15827         ClearHighlights();/*!! could figure this out*/
15828         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
15829         SendToProgram("remove\n", &first);
15830         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
15831         break;
15832
15833       case BeginningOfGame:
15834       default:
15835         break;
15836
15837       case IcsPlayingWhite:
15838       case IcsPlayingBlack:
15839         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
15840             SendToICS(ics_prefix);
15841             SendToICS("takeback 2\n");
15842         } else {
15843             SendToICS(ics_prefix);
15844             SendToICS("takeback 1\n");
15845         }
15846         break;
15847     }
15848 }
15849
15850 void
15851 MoveNowEvent ()
15852 {
15853     ChessProgramState *cps;
15854
15855     switch (gameMode) {
15856       case MachinePlaysWhite:
15857         if (!WhiteOnMove(forwardMostMove)) {
15858             DisplayError(_("It is your turn"), 0);
15859             return;
15860         }
15861         cps = &first;
15862         break;
15863       case MachinePlaysBlack:
15864         if (WhiteOnMove(forwardMostMove)) {
15865             DisplayError(_("It is your turn"), 0);
15866             return;
15867         }
15868         cps = &first;
15869         break;
15870       case TwoMachinesPlay:
15871         if (WhiteOnMove(forwardMostMove) ==
15872             (first.twoMachinesColor[0] == 'w')) {
15873             cps = &first;
15874         } else {
15875             cps = &second;
15876         }
15877         break;
15878       case BeginningOfGame:
15879       default:
15880         return;
15881     }
15882     SendToProgram("?\n", cps);
15883 }
15884
15885 void
15886 TruncateGameEvent ()
15887 {
15888     EditGameEvent();
15889     if (gameMode != EditGame) return;
15890     TruncateGame();
15891 }
15892
15893 void
15894 TruncateGame ()
15895 {
15896     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
15897     if (forwardMostMove > currentMove) {
15898         if (gameInfo.resultDetails != NULL) {
15899             free(gameInfo.resultDetails);
15900             gameInfo.resultDetails = NULL;
15901             gameInfo.result = GameUnfinished;
15902         }
15903         forwardMostMove = currentMove;
15904         HistorySet(parseList, backwardMostMove, forwardMostMove,
15905                    currentMove-1);
15906     }
15907 }
15908
15909 void
15910 HintEvent ()
15911 {
15912     if (appData.noChessProgram) return;
15913     switch (gameMode) {
15914       case MachinePlaysWhite:
15915         if (WhiteOnMove(forwardMostMove)) {
15916             DisplayError(_("Wait until your turn."), 0);
15917             return;
15918         }
15919         break;
15920       case BeginningOfGame:
15921       case MachinePlaysBlack:
15922         if (!WhiteOnMove(forwardMostMove)) {
15923             DisplayError(_("Wait until your turn."), 0);
15924             return;
15925         }
15926         break;
15927       default:
15928         DisplayError(_("No hint available"), 0);
15929         return;
15930     }
15931     SendToProgram("hint\n", &first);
15932     hintRequested = TRUE;
15933 }
15934
15935 int
15936 SaveSelected (FILE *g, int dummy, char *dummy2)
15937 {
15938     ListGame * lg = (ListGame *) gameList.head;
15939     int nItem, cnt=0;
15940     FILE *f;
15941
15942     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15943         DisplayError(_("Game list not loaded or empty"), 0);
15944         return 0;
15945     }
15946
15947     creatingBook = TRUE; // suppresses stuff during load game
15948
15949     /* Get list size */
15950     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15951         if(lg->position >= 0) { // selected?
15952             LoadGame(f, nItem, "", TRUE);
15953             SaveGamePGN2(g); // leaves g open
15954             cnt++; DoEvents();
15955         }
15956         lg = (ListGame *) lg->node.succ;
15957     }
15958
15959     fclose(g);
15960     creatingBook = FALSE;
15961
15962     return cnt;
15963 }
15964
15965 void
15966 CreateBookEvent ()
15967 {
15968     ListGame * lg = (ListGame *) gameList.head;
15969     FILE *f, *g;
15970     int nItem;
15971     static int secondTime = FALSE;
15972
15973     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
15974         DisplayError(_("Game list not loaded or empty"), 0);
15975         return;
15976     }
15977
15978     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
15979         fclose(g);
15980         secondTime++;
15981         DisplayNote(_("Book file exists! Try again for overwrite."));
15982         return;
15983     }
15984
15985     creatingBook = TRUE;
15986     secondTime = FALSE;
15987
15988     /* Get list size */
15989     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
15990         if(lg->position >= 0) {
15991             LoadGame(f, nItem, "", TRUE);
15992             AddGameToBook(TRUE);
15993             DoEvents();
15994         }
15995         lg = (ListGame *) lg->node.succ;
15996     }
15997
15998     creatingBook = FALSE;
15999     FlushBook();
16000 }
16001
16002 void
16003 BookEvent ()
16004 {
16005     if (appData.noChessProgram) return;
16006     switch (gameMode) {
16007       case MachinePlaysWhite:
16008         if (WhiteOnMove(forwardMostMove)) {
16009             DisplayError(_("Wait until your turn."), 0);
16010             return;
16011         }
16012         break;
16013       case BeginningOfGame:
16014       case MachinePlaysBlack:
16015         if (!WhiteOnMove(forwardMostMove)) {
16016             DisplayError(_("Wait until your turn."), 0);
16017             return;
16018         }
16019         break;
16020       case EditPosition:
16021         EditPositionDone(TRUE);
16022         break;
16023       case TwoMachinesPlay:
16024         return;
16025       default:
16026         break;
16027     }
16028     SendToProgram("bk\n", &first);
16029     bookOutput[0] = NULLCHAR;
16030     bookRequested = TRUE;
16031 }
16032
16033 void
16034 AboutGameEvent ()
16035 {
16036     char *tags = PGNTags(&gameInfo);
16037     TagsPopUp(tags, CmailMsg());
16038     free(tags);
16039 }
16040
16041 /* end button procedures */
16042
16043 void
16044 PrintPosition (FILE *fp, int move)
16045 {
16046     int i, j;
16047
16048     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16049         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16050             char c = PieceToChar(boards[move][i][j]);
16051             fputc(c == 'x' ? '.' : c, fp);
16052             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16053         }
16054     }
16055     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16056       fprintf(fp, "white to play\n");
16057     else
16058       fprintf(fp, "black to play\n");
16059 }
16060
16061 void
16062 PrintOpponents (FILE *fp)
16063 {
16064     if (gameInfo.white != NULL) {
16065         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16066     } else {
16067         fprintf(fp, "\n");
16068     }
16069 }
16070
16071 /* Find last component of program's own name, using some heuristics */
16072 void
16073 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16074 {
16075     char *p, *q, c;
16076     int local = (strcmp(host, "localhost") == 0);
16077     while (!local && (p = strchr(prog, ';')) != NULL) {
16078         p++;
16079         while (*p == ' ') p++;
16080         prog = p;
16081     }
16082     if (*prog == '"' || *prog == '\'') {
16083         q = strchr(prog + 1, *prog);
16084     } else {
16085         q = strchr(prog, ' ');
16086     }
16087     if (q == NULL) q = prog + strlen(prog);
16088     p = q;
16089     while (p >= prog && *p != '/' && *p != '\\') p--;
16090     p++;
16091     if(p == prog && *p == '"') p++;
16092     c = *q; *q = 0;
16093     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16094     memcpy(buf, p, q - p);
16095     buf[q - p] = NULLCHAR;
16096     if (!local) {
16097         strcat(buf, "@");
16098         strcat(buf, host);
16099     }
16100 }
16101
16102 char *
16103 TimeControlTagValue ()
16104 {
16105     char buf[MSG_SIZ];
16106     if (!appData.clockMode) {
16107       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16108     } else if (movesPerSession > 0) {
16109       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16110     } else if (timeIncrement == 0) {
16111       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16112     } else {
16113       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16114     }
16115     return StrSave(buf);
16116 }
16117
16118 void
16119 SetGameInfo ()
16120 {
16121     /* This routine is used only for certain modes */
16122     VariantClass v = gameInfo.variant;
16123     ChessMove r = GameUnfinished;
16124     char *p = NULL;
16125
16126     if(keepInfo) return;
16127
16128     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16129         r = gameInfo.result;
16130         p = gameInfo.resultDetails;
16131         gameInfo.resultDetails = NULL;
16132     }
16133     ClearGameInfo(&gameInfo);
16134     gameInfo.variant = v;
16135
16136     switch (gameMode) {
16137       case MachinePlaysWhite:
16138         gameInfo.event = StrSave( appData.pgnEventHeader );
16139         gameInfo.site = StrSave(HostName());
16140         gameInfo.date = PGNDate();
16141         gameInfo.round = StrSave("-");
16142         gameInfo.white = StrSave(first.tidy);
16143         gameInfo.black = StrSave(UserName());
16144         gameInfo.timeControl = TimeControlTagValue();
16145         break;
16146
16147       case MachinePlaysBlack:
16148         gameInfo.event = StrSave( appData.pgnEventHeader );
16149         gameInfo.site = StrSave(HostName());
16150         gameInfo.date = PGNDate();
16151         gameInfo.round = StrSave("-");
16152         gameInfo.white = StrSave(UserName());
16153         gameInfo.black = StrSave(first.tidy);
16154         gameInfo.timeControl = TimeControlTagValue();
16155         break;
16156
16157       case TwoMachinesPlay:
16158         gameInfo.event = StrSave( appData.pgnEventHeader );
16159         gameInfo.site = StrSave(HostName());
16160         gameInfo.date = PGNDate();
16161         if (roundNr > 0) {
16162             char buf[MSG_SIZ];
16163             snprintf(buf, MSG_SIZ, "%d", roundNr);
16164             gameInfo.round = StrSave(buf);
16165         } else {
16166             gameInfo.round = StrSave("-");
16167         }
16168         if (first.twoMachinesColor[0] == 'w') {
16169             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16170             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16171         } else {
16172             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16173             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16174         }
16175         gameInfo.timeControl = TimeControlTagValue();
16176         break;
16177
16178       case EditGame:
16179         gameInfo.event = StrSave("Edited game");
16180         gameInfo.site = StrSave(HostName());
16181         gameInfo.date = PGNDate();
16182         gameInfo.round = StrSave("-");
16183         gameInfo.white = StrSave("-");
16184         gameInfo.black = StrSave("-");
16185         gameInfo.result = r;
16186         gameInfo.resultDetails = p;
16187         break;
16188
16189       case EditPosition:
16190         gameInfo.event = StrSave("Edited position");
16191         gameInfo.site = StrSave(HostName());
16192         gameInfo.date = PGNDate();
16193         gameInfo.round = StrSave("-");
16194         gameInfo.white = StrSave("-");
16195         gameInfo.black = StrSave("-");
16196         break;
16197
16198       case IcsPlayingWhite:
16199       case IcsPlayingBlack:
16200       case IcsObserving:
16201       case IcsExamining:
16202         break;
16203
16204       case PlayFromGameFile:
16205         gameInfo.event = StrSave("Game from non-PGN file");
16206         gameInfo.site = StrSave(HostName());
16207         gameInfo.date = PGNDate();
16208         gameInfo.round = StrSave("-");
16209         gameInfo.white = StrSave("?");
16210         gameInfo.black = StrSave("?");
16211         break;
16212
16213       default:
16214         break;
16215     }
16216 }
16217
16218 void
16219 ReplaceComment (int index, char *text)
16220 {
16221     int len;
16222     char *p;
16223     float score;
16224
16225     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16226        pvInfoList[index-1].depth == len &&
16227        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16228        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16229     while (*text == '\n') text++;
16230     len = strlen(text);
16231     while (len > 0 && text[len - 1] == '\n') len--;
16232
16233     if (commentList[index] != NULL)
16234       free(commentList[index]);
16235
16236     if (len == 0) {
16237         commentList[index] = NULL;
16238         return;
16239     }
16240   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16241       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16242       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16243     commentList[index] = (char *) malloc(len + 2);
16244     strncpy(commentList[index], text, len);
16245     commentList[index][len] = '\n';
16246     commentList[index][len + 1] = NULLCHAR;
16247   } else {
16248     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16249     char *p;
16250     commentList[index] = (char *) malloc(len + 7);
16251     safeStrCpy(commentList[index], "{\n", 3);
16252     safeStrCpy(commentList[index]+2, text, len+1);
16253     commentList[index][len+2] = NULLCHAR;
16254     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16255     strcat(commentList[index], "\n}\n");
16256   }
16257 }
16258
16259 void
16260 CrushCRs (char *text)
16261 {
16262   char *p = text;
16263   char *q = text;
16264   char ch;
16265
16266   do {
16267     ch = *p++;
16268     if (ch == '\r') continue;
16269     *q++ = ch;
16270   } while (ch != '\0');
16271 }
16272
16273 void
16274 AppendComment (int index, char *text, Boolean addBraces)
16275 /* addBraces  tells if we should add {} */
16276 {
16277     int oldlen, len;
16278     char *old;
16279
16280 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16281     if(addBraces == 3) addBraces = 0; else // force appending literally
16282     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16283
16284     CrushCRs(text);
16285     while (*text == '\n') text++;
16286     len = strlen(text);
16287     while (len > 0 && text[len - 1] == '\n') len--;
16288     text[len] = NULLCHAR;
16289
16290     if (len == 0) return;
16291
16292     if (commentList[index] != NULL) {
16293       Boolean addClosingBrace = addBraces;
16294         old = commentList[index];
16295         oldlen = strlen(old);
16296         while(commentList[index][oldlen-1] ==  '\n')
16297           commentList[index][--oldlen] = NULLCHAR;
16298         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16299         safeStrCpy(commentList[index], old, oldlen + len + 6);
16300         free(old);
16301         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16302         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16303           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16304           while (*text == '\n') { text++; len--; }
16305           commentList[index][--oldlen] = NULLCHAR;
16306       }
16307         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16308         else          strcat(commentList[index], "\n");
16309         strcat(commentList[index], text);
16310         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16311         else          strcat(commentList[index], "\n");
16312     } else {
16313         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16314         if(addBraces)
16315           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16316         else commentList[index][0] = NULLCHAR;
16317         strcat(commentList[index], text);
16318         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16319         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16320     }
16321 }
16322
16323 static char *
16324 FindStr (char * text, char * sub_text)
16325 {
16326     char * result = strstr( text, sub_text );
16327
16328     if( result != NULL ) {
16329         result += strlen( sub_text );
16330     }
16331
16332     return result;
16333 }
16334
16335 /* [AS] Try to extract PV info from PGN comment */
16336 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16337 char *
16338 GetInfoFromComment (int index, char * text)
16339 {
16340     char * sep = text, *p;
16341
16342     if( text != NULL && index > 0 ) {
16343         int score = 0;
16344         int depth = 0;
16345         int time = -1, sec = 0, deci;
16346         char * s_eval = FindStr( text, "[%eval " );
16347         char * s_emt = FindStr( text, "[%emt " );
16348 #if 0
16349         if( s_eval != NULL || s_emt != NULL ) {
16350 #else
16351         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16352 #endif
16353             /* New style */
16354             char delim;
16355
16356             if( s_eval != NULL ) {
16357                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16358                     return text;
16359                 }
16360
16361                 if( delim != ']' ) {
16362                     return text;
16363                 }
16364             }
16365
16366             if( s_emt != NULL ) {
16367             }
16368                 return text;
16369         }
16370         else {
16371             /* We expect something like: [+|-]nnn.nn/dd */
16372             int score_lo = 0;
16373
16374             if(*text != '{') return text; // [HGM] braces: must be normal comment
16375
16376             sep = strchr( text, '/' );
16377             if( sep == NULL || sep < (text+4) ) {
16378                 return text;
16379             }
16380
16381             p = text;
16382             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16383             if(p[1] == '(') { // comment starts with PV
16384                p = strchr(p, ')'); // locate end of PV
16385                if(p == NULL || sep < p+5) return text;
16386                // at this point we have something like "{(.*) +0.23/6 ..."
16387                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16388                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16389                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16390             }
16391             time = -1; sec = -1; deci = -1;
16392             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16393                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16394                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16395                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16396                 return text;
16397             }
16398
16399             if( score_lo < 0 || score_lo >= 100 ) {
16400                 return text;
16401             }
16402
16403             if(sec >= 0) time = 600*time + 10*sec; else
16404             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16405
16406             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16407
16408             /* [HGM] PV time: now locate end of PV info */
16409             while( *++sep >= '0' && *sep <= '9'); // strip depth
16410             if(time >= 0)
16411             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16412             if(sec >= 0)
16413             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16414             if(deci >= 0)
16415             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16416             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16417         }
16418
16419         if( depth <= 0 ) {
16420             return text;
16421         }
16422
16423         if( time < 0 ) {
16424             time = -1;
16425         }
16426
16427         pvInfoList[index-1].depth = depth;
16428         pvInfoList[index-1].score = score;
16429         pvInfoList[index-1].time  = 10*time; // centi-sec
16430         if(*sep == '}') *sep = 0; else *--sep = '{';
16431         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16432     }
16433     return sep;
16434 }
16435
16436 void
16437 SendToProgram (char *message, ChessProgramState *cps)
16438 {
16439     int count, outCount, error;
16440     char buf[MSG_SIZ];
16441
16442     if (cps->pr == NoProc) return;
16443     Attention(cps);
16444
16445     if (appData.debugMode) {
16446         TimeMark now;
16447         GetTimeMark(&now);
16448         fprintf(debugFP, "%ld >%-6s: %s",
16449                 SubtractTimeMarks(&now, &programStartTime),
16450                 cps->which, message);
16451         if(serverFP)
16452             fprintf(serverFP, "%ld >%-6s: %s",
16453                 SubtractTimeMarks(&now, &programStartTime),
16454                 cps->which, message), fflush(serverFP);
16455     }
16456
16457     count = strlen(message);
16458     outCount = OutputToProcess(cps->pr, message, count, &error);
16459     if (outCount < count && !exiting
16460                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16461       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16462       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16463         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16464             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16465                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16466                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16467                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16468             } else {
16469                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16470                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16471                 gameInfo.result = res;
16472             }
16473             gameInfo.resultDetails = StrSave(buf);
16474         }
16475         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16476         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16477     }
16478 }
16479
16480 void
16481 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16482 {
16483     char *end_str;
16484     char buf[MSG_SIZ];
16485     ChessProgramState *cps = (ChessProgramState *)closure;
16486
16487     if (isr != cps->isr) return; /* Killed intentionally */
16488     if (count <= 0) {
16489         if (count == 0) {
16490             RemoveInputSource(cps->isr);
16491             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16492                     _(cps->which), cps->program);
16493             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16494             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16495                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16496                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16497                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16498                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16499                 } else {
16500                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16501                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16502                     gameInfo.result = res;
16503                 }
16504                 gameInfo.resultDetails = StrSave(buf);
16505             }
16506             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16507             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16508         } else {
16509             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16510                     _(cps->which), cps->program);
16511             RemoveInputSource(cps->isr);
16512
16513             /* [AS] Program is misbehaving badly... kill it */
16514             if( count == -2 ) {
16515                 DestroyChildProcess( cps->pr, 9 );
16516                 cps->pr = NoProc;
16517             }
16518
16519             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16520         }
16521         return;
16522     }
16523
16524     if ((end_str = strchr(message, '\r')) != NULL)
16525       *end_str = NULLCHAR;
16526     if ((end_str = strchr(message, '\n')) != NULL)
16527       *end_str = NULLCHAR;
16528
16529     if (appData.debugMode) {
16530         TimeMark now; int print = 1;
16531         char *quote = ""; char c; int i;
16532
16533         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16534                 char start = message[0];
16535                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16536                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16537                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16538                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16539                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16540                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16541                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16542                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16543                    sscanf(message, "hint: %c", &c)!=1 &&
16544                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16545                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16546                     print = (appData.engineComments >= 2);
16547                 }
16548                 message[0] = start; // restore original message
16549         }
16550         if(print) {
16551                 GetTimeMark(&now);
16552                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16553                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16554                         quote,
16555                         message);
16556                 if(serverFP)
16557                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16558                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16559                         quote,
16560                         message), fflush(serverFP);
16561         }
16562     }
16563
16564     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16565     if (appData.icsEngineAnalyze) {
16566         if (strstr(message, "whisper") != NULL ||
16567              strstr(message, "kibitz") != NULL ||
16568             strstr(message, "tellics") != NULL) return;
16569     }
16570
16571     HandleMachineMove(message, cps);
16572 }
16573
16574
16575 void
16576 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16577 {
16578     char buf[MSG_SIZ];
16579     int seconds;
16580
16581     if( timeControl_2 > 0 ) {
16582         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16583             tc = timeControl_2;
16584         }
16585     }
16586     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16587     inc /= cps->timeOdds;
16588     st  /= cps->timeOdds;
16589
16590     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16591
16592     if (st > 0) {
16593       /* Set exact time per move, normally using st command */
16594       if (cps->stKludge) {
16595         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16596         seconds = st % 60;
16597         if (seconds == 0) {
16598           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16599         } else {
16600           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16601         }
16602       } else {
16603         snprintf(buf, MSG_SIZ, "st %d\n", st);
16604       }
16605     } else {
16606       /* Set conventional or incremental time control, using level command */
16607       if (seconds == 0) {
16608         /* Note old gnuchess bug -- minutes:seconds used to not work.
16609            Fixed in later versions, but still avoid :seconds
16610            when seconds is 0. */
16611         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16612       } else {
16613         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16614                  seconds, inc/1000.);
16615       }
16616     }
16617     SendToProgram(buf, cps);
16618
16619     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16620     /* Orthogonally, limit search to given depth */
16621     if (sd > 0) {
16622       if (cps->sdKludge) {
16623         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16624       } else {
16625         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16626       }
16627       SendToProgram(buf, cps);
16628     }
16629
16630     if(cps->nps >= 0) { /* [HGM] nps */
16631         if(cps->supportsNPS == FALSE)
16632           cps->nps = -1; // don't use if engine explicitly says not supported!
16633         else {
16634           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16635           SendToProgram(buf, cps);
16636         }
16637     }
16638 }
16639
16640 ChessProgramState *
16641 WhitePlayer ()
16642 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16643 {
16644     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16645        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16646         return &second;
16647     return &first;
16648 }
16649
16650 void
16651 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16652 {
16653     char message[MSG_SIZ];
16654     long time, otime;
16655
16656     /* Note: this routine must be called when the clocks are stopped
16657        or when they have *just* been set or switched; otherwise
16658        it will be off by the time since the current tick started.
16659     */
16660     if (machineWhite) {
16661         time = whiteTimeRemaining / 10;
16662         otime = blackTimeRemaining / 10;
16663     } else {
16664         time = blackTimeRemaining / 10;
16665         otime = whiteTimeRemaining / 10;
16666     }
16667     /* [HGM] translate opponent's time by time-odds factor */
16668     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16669
16670     if (time <= 0) time = 1;
16671     if (otime <= 0) otime = 1;
16672
16673     snprintf(message, MSG_SIZ, "time %ld\n", time);
16674     SendToProgram(message, cps);
16675
16676     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16677     SendToProgram(message, cps);
16678 }
16679
16680 char *
16681 EngineDefinedVariant (ChessProgramState *cps, int n)
16682 {   // return name of n-th unknown variant that engine supports
16683     static char buf[MSG_SIZ];
16684     char *p, *s = cps->variants;
16685     if(!s) return NULL;
16686     do { // parse string from variants feature
16687       VariantClass v;
16688         p = strchr(s, ',');
16689         if(p) *p = NULLCHAR;
16690       v = StringToVariant(s);
16691       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16692         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16693             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16694                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16695                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16696                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16697             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16698         }
16699         if(p) *p++ = ',';
16700         if(n < 0) return buf;
16701     } while(s = p);
16702     return NULL;
16703 }
16704
16705 int
16706 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16707 {
16708   char buf[MSG_SIZ];
16709   int len = strlen(name);
16710   int val;
16711
16712   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16713     (*p) += len + 1;
16714     sscanf(*p, "%d", &val);
16715     *loc = (val != 0);
16716     while (**p && **p != ' ')
16717       (*p)++;
16718     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16719     SendToProgram(buf, cps);
16720     return TRUE;
16721   }
16722   return FALSE;
16723 }
16724
16725 int
16726 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16727 {
16728   char buf[MSG_SIZ];
16729   int len = strlen(name);
16730   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16731     (*p) += len + 1;
16732     sscanf(*p, "%d", loc);
16733     while (**p && **p != ' ') (*p)++;
16734     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16735     SendToProgram(buf, cps);
16736     return TRUE;
16737   }
16738   return FALSE;
16739 }
16740
16741 int
16742 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16743 {
16744   char buf[MSG_SIZ];
16745   int len = strlen(name);
16746   if (strncmp((*p), name, len) == 0
16747       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16748     (*p) += len + 2;
16749     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16750     sscanf(*p, "%[^\"]", *loc);
16751     while (**p && **p != '\"') (*p)++;
16752     if (**p == '\"') (*p)++;
16753     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16754     SendToProgram(buf, cps);
16755     return TRUE;
16756   }
16757   return FALSE;
16758 }
16759
16760 int
16761 ParseOption (Option *opt, ChessProgramState *cps)
16762 // [HGM] options: process the string that defines an engine option, and determine
16763 // name, type, default value, and allowed value range
16764 {
16765         char *p, *q, buf[MSG_SIZ];
16766         int n, min = (-1)<<31, max = 1<<31, def;
16767
16768         if(p = strstr(opt->name, " -spin ")) {
16769             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16770             if(max < min) max = min; // enforce consistency
16771             if(def < min) def = min;
16772             if(def > max) def = max;
16773             opt->value = def;
16774             opt->min = min;
16775             opt->max = max;
16776             opt->type = Spin;
16777         } else if((p = strstr(opt->name, " -slider "))) {
16778             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16779             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16780             if(max < min) max = min; // enforce consistency
16781             if(def < min) def = min;
16782             if(def > max) def = max;
16783             opt->value = def;
16784             opt->min = min;
16785             opt->max = max;
16786             opt->type = Spin; // Slider;
16787         } else if((p = strstr(opt->name, " -string "))) {
16788             opt->textValue = p+9;
16789             opt->type = TextBox;
16790         } else if((p = strstr(opt->name, " -file "))) {
16791             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16792             opt->textValue = p+7;
16793             opt->type = FileName; // FileName;
16794         } else if((p = strstr(opt->name, " -path "))) {
16795             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
16796             opt->textValue = p+7;
16797             opt->type = PathName; // PathName;
16798         } else if(p = strstr(opt->name, " -check ")) {
16799             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
16800             opt->value = (def != 0);
16801             opt->type = CheckBox;
16802         } else if(p = strstr(opt->name, " -combo ")) {
16803             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
16804             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
16805             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
16806             opt->value = n = 0;
16807             while(q = StrStr(q, " /// ")) {
16808                 n++; *q = 0;    // count choices, and null-terminate each of them
16809                 q += 5;
16810                 if(*q == '*') { // remember default, which is marked with * prefix
16811                     q++;
16812                     opt->value = n;
16813                 }
16814                 cps->comboList[cps->comboCnt++] = q;
16815             }
16816             cps->comboList[cps->comboCnt++] = NULL;
16817             opt->max = n + 1;
16818             opt->type = ComboBox;
16819         } else if(p = strstr(opt->name, " -button")) {
16820             opt->type = Button;
16821         } else if(p = strstr(opt->name, " -save")) {
16822             opt->type = SaveButton;
16823         } else return FALSE;
16824         *p = 0; // terminate option name
16825         // now look if the command-line options define a setting for this engine option.
16826         if(cps->optionSettings && cps->optionSettings[0])
16827             p = strstr(cps->optionSettings, opt->name); else p = NULL;
16828         if(p && (p == cps->optionSettings || p[-1] == ',')) {
16829           snprintf(buf, MSG_SIZ, "option %s", p);
16830                 if(p = strstr(buf, ",")) *p = 0;
16831                 if(q = strchr(buf, '=')) switch(opt->type) {
16832                     case ComboBox:
16833                         for(n=0; n<opt->max; n++)
16834                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
16835                         break;
16836                     case TextBox:
16837                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
16838                         break;
16839                     case Spin:
16840                     case CheckBox:
16841                         opt->value = atoi(q+1);
16842                     default:
16843                         break;
16844                 }
16845                 strcat(buf, "\n");
16846                 SendToProgram(buf, cps);
16847         }
16848         return TRUE;
16849 }
16850
16851 void
16852 FeatureDone (ChessProgramState *cps, int val)
16853 {
16854   DelayedEventCallback cb = GetDelayedEvent();
16855   if ((cb == InitBackEnd3 && cps == &first) ||
16856       (cb == SettingsMenuIfReady && cps == &second) ||
16857       (cb == LoadEngine) ||
16858       (cb == TwoMachinesEventIfReady)) {
16859     CancelDelayedEvent();
16860     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
16861   }
16862   cps->initDone = val;
16863   if(val) cps->reload = FALSE;
16864 }
16865
16866 /* Parse feature command from engine */
16867 void
16868 ParseFeatures (char *args, ChessProgramState *cps)
16869 {
16870   char *p = args;
16871   char *q = NULL;
16872   int val;
16873   char buf[MSG_SIZ];
16874
16875   for (;;) {
16876     while (*p == ' ') p++;
16877     if (*p == NULLCHAR) return;
16878
16879     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
16880     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
16881     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
16882     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
16883     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
16884     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
16885     if (BoolFeature(&p, "reuse", &val, cps)) {
16886       /* Engine can disable reuse, but can't enable it if user said no */
16887       if (!val) cps->reuse = FALSE;
16888       continue;
16889     }
16890     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
16891     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
16892       if (gameMode == TwoMachinesPlay) {
16893         DisplayTwoMachinesTitle();
16894       } else {
16895         DisplayTitle("");
16896       }
16897       continue;
16898     }
16899     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
16900     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
16901     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
16902     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
16903     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
16904     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
16905     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
16906     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
16907     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
16908     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
16909     if (IntFeature(&p, "done", &val, cps)) {
16910       FeatureDone(cps, val);
16911       continue;
16912     }
16913     /* Added by Tord: */
16914     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
16915     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
16916     /* End of additions by Tord */
16917
16918     /* [HGM] added features: */
16919     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
16920     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
16921     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
16922     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
16923     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
16924     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
16925     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
16926     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
16927         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
16928         FREE(cps->option[cps->nrOptions].name);
16929         cps->option[cps->nrOptions].name = q; q = NULL;
16930         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
16931           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
16932             SendToProgram(buf, cps);
16933             continue;
16934         }
16935         if(cps->nrOptions >= MAX_OPTIONS) {
16936             cps->nrOptions--;
16937             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
16938             DisplayError(buf, 0);
16939         }
16940         continue;
16941     }
16942     /* End of additions by HGM */
16943
16944     /* unknown feature: complain and skip */
16945     q = p;
16946     while (*q && *q != '=') q++;
16947     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
16948     SendToProgram(buf, cps);
16949     p = q;
16950     if (*p == '=') {
16951       p++;
16952       if (*p == '\"') {
16953         p++;
16954         while (*p && *p != '\"') p++;
16955         if (*p == '\"') p++;
16956       } else {
16957         while (*p && *p != ' ') p++;
16958       }
16959     }
16960   }
16961
16962 }
16963
16964 void
16965 PeriodicUpdatesEvent (int newState)
16966 {
16967     if (newState == appData.periodicUpdates)
16968       return;
16969
16970     appData.periodicUpdates=newState;
16971
16972     /* Display type changes, so update it now */
16973 //    DisplayAnalysis();
16974
16975     /* Get the ball rolling again... */
16976     if (newState) {
16977         AnalysisPeriodicEvent(1);
16978         StartAnalysisClock();
16979     }
16980 }
16981
16982 void
16983 PonderNextMoveEvent (int newState)
16984 {
16985     if (newState == appData.ponderNextMove) return;
16986     if (gameMode == EditPosition) EditPositionDone(TRUE);
16987     if (newState) {
16988         SendToProgram("hard\n", &first);
16989         if (gameMode == TwoMachinesPlay) {
16990             SendToProgram("hard\n", &second);
16991         }
16992     } else {
16993         SendToProgram("easy\n", &first);
16994         thinkOutput[0] = NULLCHAR;
16995         if (gameMode == TwoMachinesPlay) {
16996             SendToProgram("easy\n", &second);
16997         }
16998     }
16999     appData.ponderNextMove = newState;
17000 }
17001
17002 void
17003 NewSettingEvent (int option, int *feature, char *command, int value)
17004 {
17005     char buf[MSG_SIZ];
17006
17007     if (gameMode == EditPosition) EditPositionDone(TRUE);
17008     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17009     if(feature == NULL || *feature) SendToProgram(buf, &first);
17010     if (gameMode == TwoMachinesPlay) {
17011         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17012     }
17013 }
17014
17015 void
17016 ShowThinkingEvent ()
17017 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17018 {
17019     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17020     int newState = appData.showThinking
17021         // [HGM] thinking: other features now need thinking output as well
17022         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17023
17024     if (oldState == newState) return;
17025     oldState = newState;
17026     if (gameMode == EditPosition) EditPositionDone(TRUE);
17027     if (oldState) {
17028         SendToProgram("post\n", &first);
17029         if (gameMode == TwoMachinesPlay) {
17030             SendToProgram("post\n", &second);
17031         }
17032     } else {
17033         SendToProgram("nopost\n", &first);
17034         thinkOutput[0] = NULLCHAR;
17035         if (gameMode == TwoMachinesPlay) {
17036             SendToProgram("nopost\n", &second);
17037         }
17038     }
17039 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17040 }
17041
17042 void
17043 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17044 {
17045   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17046   if (pr == NoProc) return;
17047   AskQuestion(title, question, replyPrefix, pr);
17048 }
17049
17050 void
17051 TypeInEvent (char firstChar)
17052 {
17053     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17054         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17055         gameMode == AnalyzeMode || gameMode == EditGame ||
17056         gameMode == EditPosition || gameMode == IcsExamining ||
17057         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17058         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17059                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17060                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17061         gameMode == Training) PopUpMoveDialog(firstChar);
17062 }
17063
17064 void
17065 TypeInDoneEvent (char *move)
17066 {
17067         Board board;
17068         int n, fromX, fromY, toX, toY;
17069         char promoChar;
17070         ChessMove moveType;
17071
17072         // [HGM] FENedit
17073         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17074                 EditPositionPasteFEN(move);
17075                 return;
17076         }
17077         // [HGM] movenum: allow move number to be typed in any mode
17078         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17079           ToNrEvent(2*n-1);
17080           return;
17081         }
17082         // undocumented kludge: allow command-line option to be typed in!
17083         // (potentially fatal, and does not implement the effect of the option.)
17084         // should only be used for options that are values on which future decisions will be made,
17085         // and definitely not on options that would be used during initialization.
17086         if(strstr(move, "!!! -") == move) {
17087             ParseArgsFromString(move+4);
17088             return;
17089         }
17090
17091       if (gameMode != EditGame && currentMove != forwardMostMove &&
17092         gameMode != Training) {
17093         DisplayMoveError(_("Displayed move is not current"));
17094       } else {
17095         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17096           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17097         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17098         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17099           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17100           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17101         } else {
17102           DisplayMoveError(_("Could not parse move"));
17103         }
17104       }
17105 }
17106
17107 void
17108 DisplayMove (int moveNumber)
17109 {
17110     char message[MSG_SIZ];
17111     char res[MSG_SIZ];
17112     char cpThinkOutput[MSG_SIZ];
17113
17114     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17115
17116     if (moveNumber == forwardMostMove - 1 ||
17117         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17118
17119         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17120
17121         if (strchr(cpThinkOutput, '\n')) {
17122             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17123         }
17124     } else {
17125         *cpThinkOutput = NULLCHAR;
17126     }
17127
17128     /* [AS] Hide thinking from human user */
17129     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17130         *cpThinkOutput = NULLCHAR;
17131         if( thinkOutput[0] != NULLCHAR ) {
17132             int i;
17133
17134             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17135                 cpThinkOutput[i] = '.';
17136             }
17137             cpThinkOutput[i] = NULLCHAR;
17138             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17139         }
17140     }
17141
17142     if (moveNumber == forwardMostMove - 1 &&
17143         gameInfo.resultDetails != NULL) {
17144         if (gameInfo.resultDetails[0] == NULLCHAR) {
17145           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17146         } else {
17147           snprintf(res, MSG_SIZ, " {%s} %s",
17148                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17149         }
17150     } else {
17151         res[0] = NULLCHAR;
17152     }
17153
17154     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17155         DisplayMessage(res, cpThinkOutput);
17156     } else {
17157       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17158                 WhiteOnMove(moveNumber) ? " " : ".. ",
17159                 parseList[moveNumber], res);
17160         DisplayMessage(message, cpThinkOutput);
17161     }
17162 }
17163
17164 void
17165 DisplayComment (int moveNumber, char *text)
17166 {
17167     char title[MSG_SIZ];
17168
17169     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17170       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17171     } else {
17172       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17173               WhiteOnMove(moveNumber) ? " " : ".. ",
17174               parseList[moveNumber]);
17175     }
17176     if (text != NULL && (appData.autoDisplayComment || commentUp))
17177         CommentPopUp(title, text);
17178 }
17179
17180 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17181  * might be busy thinking or pondering.  It can be omitted if your
17182  * gnuchess is configured to stop thinking immediately on any user
17183  * input.  However, that gnuchess feature depends on the FIONREAD
17184  * ioctl, which does not work properly on some flavors of Unix.
17185  */
17186 void
17187 Attention (ChessProgramState *cps)
17188 {
17189 #if ATTENTION
17190     if (!cps->useSigint) return;
17191     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17192     switch (gameMode) {
17193       case MachinePlaysWhite:
17194       case MachinePlaysBlack:
17195       case TwoMachinesPlay:
17196       case IcsPlayingWhite:
17197       case IcsPlayingBlack:
17198       case AnalyzeMode:
17199       case AnalyzeFile:
17200         /* Skip if we know it isn't thinking */
17201         if (!cps->maybeThinking) return;
17202         if (appData.debugMode)
17203           fprintf(debugFP, "Interrupting %s\n", cps->which);
17204         InterruptChildProcess(cps->pr);
17205         cps->maybeThinking = FALSE;
17206         break;
17207       default:
17208         break;
17209     }
17210 #endif /*ATTENTION*/
17211 }
17212
17213 int
17214 CheckFlags ()
17215 {
17216     if (whiteTimeRemaining <= 0) {
17217         if (!whiteFlag) {
17218             whiteFlag = TRUE;
17219             if (appData.icsActive) {
17220                 if (appData.autoCallFlag &&
17221                     gameMode == IcsPlayingBlack && !blackFlag) {
17222                   SendToICS(ics_prefix);
17223                   SendToICS("flag\n");
17224                 }
17225             } else {
17226                 if (blackFlag) {
17227                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17228                 } else {
17229                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17230                     if (appData.autoCallFlag) {
17231                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17232                         return TRUE;
17233                     }
17234                 }
17235             }
17236         }
17237     }
17238     if (blackTimeRemaining <= 0) {
17239         if (!blackFlag) {
17240             blackFlag = TRUE;
17241             if (appData.icsActive) {
17242                 if (appData.autoCallFlag &&
17243                     gameMode == IcsPlayingWhite && !whiteFlag) {
17244                   SendToICS(ics_prefix);
17245                   SendToICS("flag\n");
17246                 }
17247             } else {
17248                 if (whiteFlag) {
17249                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17250                 } else {
17251                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17252                     if (appData.autoCallFlag) {
17253                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17254                         return TRUE;
17255                     }
17256                 }
17257             }
17258         }
17259     }
17260     return FALSE;
17261 }
17262
17263 void
17264 CheckTimeControl ()
17265 {
17266     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17267         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17268
17269     /*
17270      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17271      */
17272     if ( !WhiteOnMove(forwardMostMove) ) {
17273         /* White made time control */
17274         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17275         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17276         /* [HGM] time odds: correct new time quota for time odds! */
17277                                             / WhitePlayer()->timeOdds;
17278         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17279     } else {
17280         lastBlack -= blackTimeRemaining;
17281         /* Black made time control */
17282         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17283                                             / WhitePlayer()->other->timeOdds;
17284         lastWhite = whiteTimeRemaining;
17285     }
17286 }
17287
17288 void
17289 DisplayBothClocks ()
17290 {
17291     int wom = gameMode == EditPosition ?
17292       !blackPlaysFirst : WhiteOnMove(currentMove);
17293     DisplayWhiteClock(whiteTimeRemaining, wom);
17294     DisplayBlackClock(blackTimeRemaining, !wom);
17295 }
17296
17297
17298 /* Timekeeping seems to be a portability nightmare.  I think everyone
17299    has ftime(), but I'm really not sure, so I'm including some ifdefs
17300    to use other calls if you don't.  Clocks will be less accurate if
17301    you have neither ftime nor gettimeofday.
17302 */
17303
17304 /* VS 2008 requires the #include outside of the function */
17305 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17306 #include <sys/timeb.h>
17307 #endif
17308
17309 /* Get the current time as a TimeMark */
17310 void
17311 GetTimeMark (TimeMark *tm)
17312 {
17313 #if HAVE_GETTIMEOFDAY
17314
17315     struct timeval timeVal;
17316     struct timezone timeZone;
17317
17318     gettimeofday(&timeVal, &timeZone);
17319     tm->sec = (long) timeVal.tv_sec;
17320     tm->ms = (int) (timeVal.tv_usec / 1000L);
17321
17322 #else /*!HAVE_GETTIMEOFDAY*/
17323 #if HAVE_FTIME
17324
17325 // include <sys/timeb.h> / moved to just above start of function
17326     struct timeb timeB;
17327
17328     ftime(&timeB);
17329     tm->sec = (long) timeB.time;
17330     tm->ms = (int) timeB.millitm;
17331
17332 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17333     tm->sec = (long) time(NULL);
17334     tm->ms = 0;
17335 #endif
17336 #endif
17337 }
17338
17339 /* Return the difference in milliseconds between two
17340    time marks.  We assume the difference will fit in a long!
17341 */
17342 long
17343 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17344 {
17345     return 1000L*(tm2->sec - tm1->sec) +
17346            (long) (tm2->ms - tm1->ms);
17347 }
17348
17349
17350 /*
17351  * Code to manage the game clocks.
17352  *
17353  * In tournament play, black starts the clock and then white makes a move.
17354  * We give the human user a slight advantage if he is playing white---the
17355  * clocks don't run until he makes his first move, so it takes zero time.
17356  * Also, we don't account for network lag, so we could get out of sync
17357  * with GNU Chess's clock -- but then, referees are always right.
17358  */
17359
17360 static TimeMark tickStartTM;
17361 static long intendedTickLength;
17362
17363 long
17364 NextTickLength (long timeRemaining)
17365 {
17366     long nominalTickLength, nextTickLength;
17367
17368     if (timeRemaining > 0L && timeRemaining <= 10000L)
17369       nominalTickLength = 100L;
17370     else
17371       nominalTickLength = 1000L;
17372     nextTickLength = timeRemaining % nominalTickLength;
17373     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17374
17375     return nextTickLength;
17376 }
17377
17378 /* Adjust clock one minute up or down */
17379 void
17380 AdjustClock (Boolean which, int dir)
17381 {
17382     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17383     if(which) blackTimeRemaining += 60000*dir;
17384     else      whiteTimeRemaining += 60000*dir;
17385     DisplayBothClocks();
17386     adjustedClock = TRUE;
17387 }
17388
17389 /* Stop clocks and reset to a fresh time control */
17390 void
17391 ResetClocks ()
17392 {
17393     (void) StopClockTimer();
17394     if (appData.icsActive) {
17395         whiteTimeRemaining = blackTimeRemaining = 0;
17396     } else if (searchTime) {
17397         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17398         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17399     } else { /* [HGM] correct new time quote for time odds */
17400         whiteTC = blackTC = fullTimeControlString;
17401         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17402         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17403     }
17404     if (whiteFlag || blackFlag) {
17405         DisplayTitle("");
17406         whiteFlag = blackFlag = FALSE;
17407     }
17408     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17409     DisplayBothClocks();
17410     adjustedClock = FALSE;
17411 }
17412
17413 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17414
17415 /* Decrement running clock by amount of time that has passed */
17416 void
17417 DecrementClocks ()
17418 {
17419     long timeRemaining;
17420     long lastTickLength, fudge;
17421     TimeMark now;
17422
17423     if (!appData.clockMode) return;
17424     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17425
17426     GetTimeMark(&now);
17427
17428     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17429
17430     /* Fudge if we woke up a little too soon */
17431     fudge = intendedTickLength - lastTickLength;
17432     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17433
17434     if (WhiteOnMove(forwardMostMove)) {
17435         if(whiteNPS >= 0) lastTickLength = 0;
17436         timeRemaining = whiteTimeRemaining -= lastTickLength;
17437         if(timeRemaining < 0 && !appData.icsActive) {
17438             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17439             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17440                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17441                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17442             }
17443         }
17444         DisplayWhiteClock(whiteTimeRemaining - fudge,
17445                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17446     } else {
17447         if(blackNPS >= 0) lastTickLength = 0;
17448         timeRemaining = blackTimeRemaining -= lastTickLength;
17449         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17450             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17451             if(suddenDeath) {
17452                 blackStartMove = forwardMostMove;
17453                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17454             }
17455         }
17456         DisplayBlackClock(blackTimeRemaining - fudge,
17457                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17458     }
17459     if (CheckFlags()) return;
17460
17461     if(twoBoards) { // count down secondary board's clocks as well
17462         activePartnerTime -= lastTickLength;
17463         partnerUp = 1;
17464         if(activePartner == 'W')
17465             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17466         else
17467             DisplayBlackClock(activePartnerTime, TRUE);
17468         partnerUp = 0;
17469     }
17470
17471     tickStartTM = now;
17472     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17473     StartClockTimer(intendedTickLength);
17474
17475     /* if the time remaining has fallen below the alarm threshold, sound the
17476      * alarm. if the alarm has sounded and (due to a takeback or time control
17477      * with increment) the time remaining has increased to a level above the
17478      * threshold, reset the alarm so it can sound again.
17479      */
17480
17481     if (appData.icsActive && appData.icsAlarm) {
17482
17483         /* make sure we are dealing with the user's clock */
17484         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17485                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17486            )) return;
17487
17488         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17489             alarmSounded = FALSE;
17490         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17491             PlayAlarmSound();
17492             alarmSounded = TRUE;
17493         }
17494     }
17495 }
17496
17497
17498 /* A player has just moved, so stop the previously running
17499    clock and (if in clock mode) start the other one.
17500    We redisplay both clocks in case we're in ICS mode, because
17501    ICS gives us an update to both clocks after every move.
17502    Note that this routine is called *after* forwardMostMove
17503    is updated, so the last fractional tick must be subtracted
17504    from the color that is *not* on move now.
17505 */
17506 void
17507 SwitchClocks (int newMoveNr)
17508 {
17509     long lastTickLength;
17510     TimeMark now;
17511     int flagged = FALSE;
17512
17513     GetTimeMark(&now);
17514
17515     if (StopClockTimer() && appData.clockMode) {
17516         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17517         if (!WhiteOnMove(forwardMostMove)) {
17518             if(blackNPS >= 0) lastTickLength = 0;
17519             blackTimeRemaining -= lastTickLength;
17520            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17521 //         if(pvInfoList[forwardMostMove].time == -1)
17522                  pvInfoList[forwardMostMove].time =               // use GUI time
17523                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17524         } else {
17525            if(whiteNPS >= 0) lastTickLength = 0;
17526            whiteTimeRemaining -= lastTickLength;
17527            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17528 //         if(pvInfoList[forwardMostMove].time == -1)
17529                  pvInfoList[forwardMostMove].time =
17530                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17531         }
17532         flagged = CheckFlags();
17533     }
17534     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17535     CheckTimeControl();
17536
17537     if (flagged || !appData.clockMode) return;
17538
17539     switch (gameMode) {
17540       case MachinePlaysBlack:
17541       case MachinePlaysWhite:
17542       case BeginningOfGame:
17543         if (pausing) return;
17544         break;
17545
17546       case EditGame:
17547       case PlayFromGameFile:
17548       case IcsExamining:
17549         return;
17550
17551       default:
17552         break;
17553     }
17554
17555     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17556         if(WhiteOnMove(forwardMostMove))
17557              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17558         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17559     }
17560
17561     tickStartTM = now;
17562     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17563       whiteTimeRemaining : blackTimeRemaining);
17564     StartClockTimer(intendedTickLength);
17565 }
17566
17567
17568 /* Stop both clocks */
17569 void
17570 StopClocks ()
17571 {
17572     long lastTickLength;
17573     TimeMark now;
17574
17575     if (!StopClockTimer()) return;
17576     if (!appData.clockMode) return;
17577
17578     GetTimeMark(&now);
17579
17580     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17581     if (WhiteOnMove(forwardMostMove)) {
17582         if(whiteNPS >= 0) lastTickLength = 0;
17583         whiteTimeRemaining -= lastTickLength;
17584         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17585     } else {
17586         if(blackNPS >= 0) lastTickLength = 0;
17587         blackTimeRemaining -= lastTickLength;
17588         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17589     }
17590     CheckFlags();
17591 }
17592
17593 /* Start clock of player on move.  Time may have been reset, so
17594    if clock is already running, stop and restart it. */
17595 void
17596 StartClocks ()
17597 {
17598     (void) StopClockTimer(); /* in case it was running already */
17599     DisplayBothClocks();
17600     if (CheckFlags()) return;
17601
17602     if (!appData.clockMode) return;
17603     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17604
17605     GetTimeMark(&tickStartTM);
17606     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17607       whiteTimeRemaining : blackTimeRemaining);
17608
17609    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17610     whiteNPS = blackNPS = -1;
17611     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17612        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17613         whiteNPS = first.nps;
17614     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17615        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17616         blackNPS = first.nps;
17617     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17618         whiteNPS = second.nps;
17619     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17620         blackNPS = second.nps;
17621     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17622
17623     StartClockTimer(intendedTickLength);
17624 }
17625
17626 char *
17627 TimeString (long ms)
17628 {
17629     long second, minute, hour, day;
17630     char *sign = "";
17631     static char buf[32];
17632
17633     if (ms > 0 && ms <= 9900) {
17634       /* convert milliseconds to tenths, rounding up */
17635       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17636
17637       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17638       return buf;
17639     }
17640
17641     /* convert milliseconds to seconds, rounding up */
17642     /* use floating point to avoid strangeness of integer division
17643        with negative dividends on many machines */
17644     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17645
17646     if (second < 0) {
17647         sign = "-";
17648         second = -second;
17649     }
17650
17651     day = second / (60 * 60 * 24);
17652     second = second % (60 * 60 * 24);
17653     hour = second / (60 * 60);
17654     second = second % (60 * 60);
17655     minute = second / 60;
17656     second = second % 60;
17657
17658     if (day > 0)
17659       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17660               sign, day, hour, minute, second);
17661     else if (hour > 0)
17662       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17663     else
17664       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17665
17666     return buf;
17667 }
17668
17669
17670 /*
17671  * This is necessary because some C libraries aren't ANSI C compliant yet.
17672  */
17673 char *
17674 StrStr (char *string, char *match)
17675 {
17676     int i, length;
17677
17678     length = strlen(match);
17679
17680     for (i = strlen(string) - length; i >= 0; i--, string++)
17681       if (!strncmp(match, string, length))
17682         return string;
17683
17684     return NULL;
17685 }
17686
17687 char *
17688 StrCaseStr (char *string, char *match)
17689 {
17690     int i, j, length;
17691
17692     length = strlen(match);
17693
17694     for (i = strlen(string) - length; i >= 0; i--, string++) {
17695         for (j = 0; j < length; j++) {
17696             if (ToLower(match[j]) != ToLower(string[j]))
17697               break;
17698         }
17699         if (j == length) return string;
17700     }
17701
17702     return NULL;
17703 }
17704
17705 #ifndef _amigados
17706 int
17707 StrCaseCmp (char *s1, char *s2)
17708 {
17709     char c1, c2;
17710
17711     for (;;) {
17712         c1 = ToLower(*s1++);
17713         c2 = ToLower(*s2++);
17714         if (c1 > c2) return 1;
17715         if (c1 < c2) return -1;
17716         if (c1 == NULLCHAR) return 0;
17717     }
17718 }
17719
17720
17721 int
17722 ToLower (int c)
17723 {
17724     return isupper(c) ? tolower(c) : c;
17725 }
17726
17727
17728 int
17729 ToUpper (int c)
17730 {
17731     return islower(c) ? toupper(c) : c;
17732 }
17733 #endif /* !_amigados    */
17734
17735 char *
17736 StrSave (char *s)
17737 {
17738   char *ret;
17739
17740   if ((ret = (char *) malloc(strlen(s) + 1)))
17741     {
17742       safeStrCpy(ret, s, strlen(s)+1);
17743     }
17744   return ret;
17745 }
17746
17747 char *
17748 StrSavePtr (char *s, char **savePtr)
17749 {
17750     if (*savePtr) {
17751         free(*savePtr);
17752     }
17753     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17754       safeStrCpy(*savePtr, s, strlen(s)+1);
17755     }
17756     return(*savePtr);
17757 }
17758
17759 char *
17760 PGNDate ()
17761 {
17762     time_t clock;
17763     struct tm *tm;
17764     char buf[MSG_SIZ];
17765
17766     clock = time((time_t *)NULL);
17767     tm = localtime(&clock);
17768     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17769             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17770     return StrSave(buf);
17771 }
17772
17773
17774 char *
17775 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17776 {
17777     int i, j, fromX, fromY, toX, toY;
17778     int whiteToPlay;
17779     char buf[MSG_SIZ];
17780     char *p, *q;
17781     int emptycount;
17782     ChessSquare piece;
17783
17784     whiteToPlay = (gameMode == EditPosition) ?
17785       !blackPlaysFirst : (move % 2 == 0);
17786     p = buf;
17787
17788     /* Piece placement data */
17789     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17790         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
17791         emptycount = 0;
17792         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
17793             if (boards[move][i][j] == EmptySquare) {
17794                 emptycount++;
17795             } else { ChessSquare piece = boards[move][i][j];
17796                 if (emptycount > 0) {
17797                     if(emptycount<10) /* [HGM] can be >= 10 */
17798                         *p++ = '0' + emptycount;
17799                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17800                     emptycount = 0;
17801                 }
17802                 if(PieceToChar(piece) == '+') {
17803                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
17804                     *p++ = '+';
17805                     piece = (ChessSquare)(CHUDEMOTED piece);
17806                 }
17807                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
17808                 if(p[-1] == '~') {
17809                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
17810                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
17811                     *p++ = '~';
17812                 }
17813             }
17814         }
17815         if (emptycount > 0) {
17816             if(emptycount<10) /* [HGM] can be >= 10 */
17817                 *p++ = '0' + emptycount;
17818             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
17819             emptycount = 0;
17820         }
17821         *p++ = '/';
17822     }
17823     *(p - 1) = ' ';
17824
17825     /* [HGM] print Crazyhouse or Shogi holdings */
17826     if( gameInfo.holdingsWidth ) {
17827         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
17828         q = p;
17829         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
17830             piece = boards[move][i][BOARD_WIDTH-1];
17831             if( piece != EmptySquare )
17832               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
17833                   *p++ = PieceToChar(piece);
17834         }
17835         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
17836             piece = boards[move][BOARD_HEIGHT-i-1][0];
17837             if( piece != EmptySquare )
17838               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
17839                   *p++ = PieceToChar(piece);
17840         }
17841
17842         if( q == p ) *p++ = '-';
17843         *p++ = ']';
17844         *p++ = ' ';
17845     }
17846
17847     /* Active color */
17848     *p++ = whiteToPlay ? 'w' : 'b';
17849     *p++ = ' ';
17850
17851   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
17852     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
17853   } else {
17854   if(nrCastlingRights) {
17855      int handW=0, handB=0;
17856      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
17857         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
17858         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
17859      }
17860      q = p;
17861      if(appData.fischerCastling) {
17862         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
17863            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17864                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17865         } else {
17866        /* [HGM] write directly from rights */
17867            if(boards[move][CASTLING][2] != NoRights &&
17868               boards[move][CASTLING][0] != NoRights   )
17869                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
17870            if(boards[move][CASTLING][2] != NoRights &&
17871               boards[move][CASTLING][1] != NoRights   )
17872                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
17873         }
17874         if(handB) {
17875            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
17876                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17877         } else {
17878            if(boards[move][CASTLING][5] != NoRights &&
17879               boards[move][CASTLING][3] != NoRights   )
17880                 *p++ = boards[move][CASTLING][3] + AAA;
17881            if(boards[move][CASTLING][5] != NoRights &&
17882               boards[move][CASTLING][4] != NoRights   )
17883                 *p++ = boards[move][CASTLING][4] + AAA;
17884         }
17885      } else {
17886
17887         /* [HGM] write true castling rights */
17888         if( nrCastlingRights == 6 ) {
17889             int q, k=0;
17890             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
17891                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
17892             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
17893                  boards[move][CASTLING][2] != NoRights  );
17894             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
17895                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17896                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
17897                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
17898             }
17899             if(q) *p++ = 'Q';
17900             k = 0;
17901             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
17902                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
17903             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
17904                  boards[move][CASTLING][5] != NoRights  );
17905             if(handB) {
17906                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
17907                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
17908                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
17909             }
17910             if(q) *p++ = 'q';
17911         }
17912      }
17913      if (q == p) *p++ = '-'; /* No castling rights */
17914      *p++ = ' ';
17915   }
17916
17917   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
17918      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
17919      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
17920     /* En passant target square */
17921     if (move > backwardMostMove) {
17922         fromX = moveList[move - 1][0] - AAA;
17923         fromY = moveList[move - 1][1] - ONE;
17924         toX = moveList[move - 1][2] - AAA;
17925         toY = moveList[move - 1][3] - ONE;
17926         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
17927             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
17928             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
17929             fromX == toX) {
17930             /* 2-square pawn move just happened */
17931             *p++ = toX + AAA;
17932             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17933         } else {
17934             *p++ = '-';
17935         }
17936     } else if(move == backwardMostMove) {
17937         // [HGM] perhaps we should always do it like this, and forget the above?
17938         if((signed char)boards[move][EP_STATUS] >= 0) {
17939             *p++ = boards[move][EP_STATUS] + AAA;
17940             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
17941         } else {
17942             *p++ = '-';
17943         }
17944     } else {
17945         *p++ = '-';
17946     }
17947     *p++ = ' ';
17948   }
17949   }
17950
17951     if(moveCounts)
17952     {   int i = 0, j=move;
17953
17954         /* [HGM] find reversible plies */
17955         if (appData.debugMode) { int k;
17956             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
17957             for(k=backwardMostMove; k<=forwardMostMove; k++)
17958                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
17959
17960         }
17961
17962         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
17963         if( j == backwardMostMove ) i += initialRulePlies;
17964         sprintf(p, "%d ", i);
17965         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
17966
17967         /* Fullmove number */
17968         sprintf(p, "%d", (move / 2) + 1);
17969     } else *--p = NULLCHAR;
17970
17971     return StrSave(buf);
17972 }
17973
17974 Boolean
17975 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
17976 {
17977     int i, j, k, w=0, subst=0, shuffle=0;
17978     char *p, c;
17979     int emptycount, virgin[BOARD_FILES];
17980     ChessSquare piece;
17981
17982     p = fen;
17983
17984     /* Piece placement data */
17985     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
17986         j = 0;
17987         for (;;) {
17988             if (*p == '/' || *p == ' ' || *p == '[' ) {
17989                 if(j > w) w = j;
17990                 emptycount = gameInfo.boardWidth - j;
17991                 while (emptycount--)
17992                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
17993                 if (*p == '/') p++;
17994                 else if(autoSize) { // we stumbled unexpectedly into end of board
17995                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
17996                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
17997                     }
17998                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
17999                 }
18000                 break;
18001 #if(BOARD_FILES >= 10)*0
18002             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18003                 p++; emptycount=10;
18004                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18005                 while (emptycount--)
18006                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18007 #endif
18008             } else if (*p == '*') {
18009                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18010             } else if (isdigit(*p)) {
18011                 emptycount = *p++ - '0';
18012                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18013                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18014                 while (emptycount--)
18015                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18016             } else if (*p == '<') {
18017                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18018                 else if (i != 0 || !shuffle) return FALSE;
18019                 p++;
18020             } else if (shuffle && *p == '>') {
18021                 p++; // for now ignore closing shuffle range, and assume rank-end
18022             } else if (*p == '?') {
18023                 if (j >= gameInfo.boardWidth) return FALSE;
18024                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18025                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18026             } else if (*p == '+' || isalpha(*p)) {
18027                 if (j >= gameInfo.boardWidth) return FALSE;
18028                 if(*p=='+') {
18029                     piece = CharToPiece(*++p);
18030                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18031                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
18032                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18033                 } else piece = CharToPiece(*p++);
18034
18035                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18036                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18037                     piece = (ChessSquare) (PROMOTED piece);
18038                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18039                     p++;
18040                 }
18041                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18042             } else {
18043                 return FALSE;
18044             }
18045         }
18046     }
18047     while (*p == '/' || *p == ' ') p++;
18048
18049     if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
18050
18051     /* [HGM] by default clear Crazyhouse holdings, if present */
18052     if(gameInfo.holdingsWidth) {
18053        for(i=0; i<BOARD_HEIGHT; i++) {
18054            board[i][0]             = EmptySquare; /* black holdings */
18055            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18056            board[i][1]             = (ChessSquare) 0; /* black counts */
18057            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18058        }
18059     }
18060
18061     /* [HGM] look for Crazyhouse holdings here */
18062     while(*p==' ') p++;
18063     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18064         int swap=0, wcnt=0, bcnt=0;
18065         if(*p == '[') p++;
18066         if(*p == '<') swap++, p++;
18067         if(*p == '-' ) p++; /* empty holdings */ else {
18068             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18069             /* if we would allow FEN reading to set board size, we would   */
18070             /* have to add holdings and shift the board read so far here   */
18071             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18072                 p++;
18073                 if((int) piece >= (int) BlackPawn ) {
18074                     i = (int)piece - (int)BlackPawn;
18075                     i = PieceToNumber((ChessSquare)i);
18076                     if( i >= gameInfo.holdingsSize ) return FALSE;
18077                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18078                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18079                     bcnt++;
18080                 } else {
18081                     i = (int)piece - (int)WhitePawn;
18082                     i = PieceToNumber((ChessSquare)i);
18083                     if( i >= gameInfo.holdingsSize ) return FALSE;
18084                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18085                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18086                     wcnt++;
18087                 }
18088             }
18089             if(subst) { // substitute back-rank question marks by holdings pieces
18090                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18091                     int k, m, n = bcnt + 1;
18092                     if(board[0][j] == ClearBoard) {
18093                         if(!wcnt) return FALSE;
18094                         n = rand() % wcnt;
18095                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18096                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18097                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18098                             break;
18099                         }
18100                     }
18101                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18102                         if(!bcnt) return FALSE;
18103                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18104                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18105                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18106                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18107                             break;
18108                         }
18109                     }
18110                 }
18111                 subst = 0;
18112             }
18113         }
18114         if(*p == ']') p++;
18115     }
18116
18117     if(subst) return FALSE; // substitution requested, but no holdings
18118
18119     while(*p == ' ') p++;
18120
18121     /* Active color */
18122     c = *p++;
18123     if(appData.colorNickNames) {
18124       if( c == appData.colorNickNames[0] ) c = 'w'; else
18125       if( c == appData.colorNickNames[1] ) c = 'b';
18126     }
18127     switch (c) {
18128       case 'w':
18129         *blackPlaysFirst = FALSE;
18130         break;
18131       case 'b':
18132         *blackPlaysFirst = TRUE;
18133         break;
18134       default:
18135         return FALSE;
18136     }
18137
18138     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18139     /* return the extra info in global variiables             */
18140
18141     /* set defaults in case FEN is incomplete */
18142     board[EP_STATUS] = EP_UNKNOWN;
18143     for(i=0; i<nrCastlingRights; i++ ) {
18144         board[CASTLING][i] =
18145             appData.fischerCastling ? NoRights : initialRights[i];
18146     }   /* assume possible unless obviously impossible */
18147     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18148     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18149     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18150                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18151     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18152     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18153     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18154                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18155     FENrulePlies = 0;
18156
18157     while(*p==' ') p++;
18158     if(nrCastlingRights) {
18159       int fischer = 0;
18160       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18161       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18162           /* castling indicator present, so default becomes no castlings */
18163           for(i=0; i<nrCastlingRights; i++ ) {
18164                  board[CASTLING][i] = NoRights;
18165           }
18166       }
18167       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18168              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18169              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18170              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18171         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18172
18173         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18174             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
18175             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
18176         }
18177         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18178             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18179         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
18180                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18181         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
18182                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
18183         switch(c) {
18184           case'K':
18185               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
18186               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18187               board[CASTLING][2] = whiteKingFile;
18188               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18189               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18190               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18191               break;
18192           case'Q':
18193               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
18194               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18195               board[CASTLING][2] = whiteKingFile;
18196               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18197               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18198               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18199               break;
18200           case'k':
18201               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
18202               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18203               board[CASTLING][5] = blackKingFile;
18204               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18205               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18206               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18207               break;
18208           case'q':
18209               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
18210               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18211               board[CASTLING][5] = blackKingFile;
18212               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18213               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18214               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18215           case '-':
18216               break;
18217           default: /* FRC castlings */
18218               if(c >= 'a') { /* black rights */
18219                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18220                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18221                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18222                   if(i == BOARD_RGHT) break;
18223                   board[CASTLING][5] = i;
18224                   c -= AAA;
18225                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18226                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18227                   if(c > i)
18228                       board[CASTLING][3] = c;
18229                   else
18230                       board[CASTLING][4] = c;
18231               } else { /* white rights */
18232                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18233                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18234                     if(board[0][i] == WhiteKing) break;
18235                   if(i == BOARD_RGHT) break;
18236                   board[CASTLING][2] = i;
18237                   c -= AAA - 'a' + 'A';
18238                   if(board[0][c] >= WhiteKing) break;
18239                   if(c > i)
18240                       board[CASTLING][0] = c;
18241                   else
18242                       board[CASTLING][1] = c;
18243               }
18244         }
18245       }
18246       for(i=0; i<nrCastlingRights; i++)
18247         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18248       if(gameInfo.variant == VariantSChess)
18249         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18250       if(fischer && shuffle) appData.fischerCastling = TRUE;
18251     if (appData.debugMode) {
18252         fprintf(debugFP, "FEN castling rights:");
18253         for(i=0; i<nrCastlingRights; i++)
18254         fprintf(debugFP, " %d", board[CASTLING][i]);
18255         fprintf(debugFP, "\n");
18256     }
18257
18258       while(*p==' ') p++;
18259     }
18260
18261     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18262
18263     /* read e.p. field in games that know e.p. capture */
18264     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18265        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18266        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18267       if(*p=='-') {
18268         p++; board[EP_STATUS] = EP_NONE;
18269       } else {
18270          char c = *p++ - AAA;
18271
18272          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18273          if(*p >= '0' && *p <='9') p++;
18274          board[EP_STATUS] = c;
18275       }
18276     }
18277
18278
18279     if(sscanf(p, "%d", &i) == 1) {
18280         FENrulePlies = i; /* 50-move ply counter */
18281         /* (The move number is still ignored)    */
18282     }
18283
18284     return TRUE;
18285 }
18286
18287 void
18288 EditPositionPasteFEN (char *fen)
18289 {
18290   if (fen != NULL) {
18291     Board initial_position;
18292
18293     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18294       DisplayError(_("Bad FEN position in clipboard"), 0);
18295       return ;
18296     } else {
18297       int savedBlackPlaysFirst = blackPlaysFirst;
18298       EditPositionEvent();
18299       blackPlaysFirst = savedBlackPlaysFirst;
18300       CopyBoard(boards[0], initial_position);
18301       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18302       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18303       DisplayBothClocks();
18304       DrawPosition(FALSE, boards[currentMove]);
18305     }
18306   }
18307 }
18308
18309 static char cseq[12] = "\\   ";
18310
18311 Boolean
18312 set_cont_sequence (char *new_seq)
18313 {
18314     int len;
18315     Boolean ret;
18316
18317     // handle bad attempts to set the sequence
18318         if (!new_seq)
18319                 return 0; // acceptable error - no debug
18320
18321     len = strlen(new_seq);
18322     ret = (len > 0) && (len < sizeof(cseq));
18323     if (ret)
18324       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18325     else if (appData.debugMode)
18326       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18327     return ret;
18328 }
18329
18330 /*
18331     reformat a source message so words don't cross the width boundary.  internal
18332     newlines are not removed.  returns the wrapped size (no null character unless
18333     included in source message).  If dest is NULL, only calculate the size required
18334     for the dest buffer.  lp argument indicats line position upon entry, and it's
18335     passed back upon exit.
18336 */
18337 int
18338 wrap (char *dest, char *src, int count, int width, int *lp)
18339 {
18340     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18341
18342     cseq_len = strlen(cseq);
18343     old_line = line = *lp;
18344     ansi = len = clen = 0;
18345
18346     for (i=0; i < count; i++)
18347     {
18348         if (src[i] == '\033')
18349             ansi = 1;
18350
18351         // if we hit the width, back up
18352         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18353         {
18354             // store i & len in case the word is too long
18355             old_i = i, old_len = len;
18356
18357             // find the end of the last word
18358             while (i && src[i] != ' ' && src[i] != '\n')
18359             {
18360                 i--;
18361                 len--;
18362             }
18363
18364             // word too long?  restore i & len before splitting it
18365             if ((old_i-i+clen) >= width)
18366             {
18367                 i = old_i;
18368                 len = old_len;
18369             }
18370
18371             // extra space?
18372             if (i && src[i-1] == ' ')
18373                 len--;
18374
18375             if (src[i] != ' ' && src[i] != '\n')
18376             {
18377                 i--;
18378                 if (len)
18379                     len--;
18380             }
18381
18382             // now append the newline and continuation sequence
18383             if (dest)
18384                 dest[len] = '\n';
18385             len++;
18386             if (dest)
18387                 strncpy(dest+len, cseq, cseq_len);
18388             len += cseq_len;
18389             line = cseq_len;
18390             clen = cseq_len;
18391             continue;
18392         }
18393
18394         if (dest)
18395             dest[len] = src[i];
18396         len++;
18397         if (!ansi)
18398             line++;
18399         if (src[i] == '\n')
18400             line = 0;
18401         if (src[i] == 'm')
18402             ansi = 0;
18403     }
18404     if (dest && appData.debugMode)
18405     {
18406         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18407             count, width, line, len, *lp);
18408         show_bytes(debugFP, src, count);
18409         fprintf(debugFP, "\ndest: ");
18410         show_bytes(debugFP, dest, len);
18411         fprintf(debugFP, "\n");
18412     }
18413     *lp = dest ? line : old_line;
18414
18415     return len;
18416 }
18417
18418 // [HGM] vari: routines for shelving variations
18419 Boolean modeRestore = FALSE;
18420
18421 void
18422 PushInner (int firstMove, int lastMove)
18423 {
18424         int i, j, nrMoves = lastMove - firstMove;
18425
18426         // push current tail of game on stack
18427         savedResult[storedGames] = gameInfo.result;
18428         savedDetails[storedGames] = gameInfo.resultDetails;
18429         gameInfo.resultDetails = NULL;
18430         savedFirst[storedGames] = firstMove;
18431         savedLast [storedGames] = lastMove;
18432         savedFramePtr[storedGames] = framePtr;
18433         framePtr -= nrMoves; // reserve space for the boards
18434         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18435             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18436             for(j=0; j<MOVE_LEN; j++)
18437                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18438             for(j=0; j<2*MOVE_LEN; j++)
18439                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18440             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18441             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18442             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18443             pvInfoList[firstMove+i-1].depth = 0;
18444             commentList[framePtr+i] = commentList[firstMove+i];
18445             commentList[firstMove+i] = NULL;
18446         }
18447
18448         storedGames++;
18449         forwardMostMove = firstMove; // truncate game so we can start variation
18450 }
18451
18452 void
18453 PushTail (int firstMove, int lastMove)
18454 {
18455         if(appData.icsActive) { // only in local mode
18456                 forwardMostMove = currentMove; // mimic old ICS behavior
18457                 return;
18458         }
18459         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18460
18461         PushInner(firstMove, lastMove);
18462         if(storedGames == 1) GreyRevert(FALSE);
18463         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18464 }
18465
18466 void
18467 PopInner (Boolean annotate)
18468 {
18469         int i, j, nrMoves;
18470         char buf[8000], moveBuf[20];
18471
18472         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18473         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18474         nrMoves = savedLast[storedGames] - currentMove;
18475         if(annotate) {
18476                 int cnt = 10;
18477                 if(!WhiteOnMove(currentMove))
18478                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18479                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18480                 for(i=currentMove; i<forwardMostMove; i++) {
18481                         if(WhiteOnMove(i))
18482                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18483                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18484                         strcat(buf, moveBuf);
18485                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18486                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18487                 }
18488                 strcat(buf, ")");
18489         }
18490         for(i=1; i<=nrMoves; i++) { // copy last variation back
18491             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18492             for(j=0; j<MOVE_LEN; j++)
18493                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18494             for(j=0; j<2*MOVE_LEN; j++)
18495                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18496             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18497             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18498             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18499             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18500             commentList[currentMove+i] = commentList[framePtr+i];
18501             commentList[framePtr+i] = NULL;
18502         }
18503         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18504         framePtr = savedFramePtr[storedGames];
18505         gameInfo.result = savedResult[storedGames];
18506         if(gameInfo.resultDetails != NULL) {
18507             free(gameInfo.resultDetails);
18508       }
18509         gameInfo.resultDetails = savedDetails[storedGames];
18510         forwardMostMove = currentMove + nrMoves;
18511 }
18512
18513 Boolean
18514 PopTail (Boolean annotate)
18515 {
18516         if(appData.icsActive) return FALSE; // only in local mode
18517         if(!storedGames) return FALSE; // sanity
18518         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18519
18520         PopInner(annotate);
18521         if(currentMove < forwardMostMove) ForwardEvent(); else
18522         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18523
18524         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18525         return TRUE;
18526 }
18527
18528 void
18529 CleanupTail ()
18530 {       // remove all shelved variations
18531         int i;
18532         for(i=0; i<storedGames; i++) {
18533             if(savedDetails[i])
18534                 free(savedDetails[i]);
18535             savedDetails[i] = NULL;
18536         }
18537         for(i=framePtr; i<MAX_MOVES; i++) {
18538                 if(commentList[i]) free(commentList[i]);
18539                 commentList[i] = NULL;
18540         }
18541         framePtr = MAX_MOVES-1;
18542         storedGames = 0;
18543 }
18544
18545 void
18546 LoadVariation (int index, char *text)
18547 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18548         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18549         int level = 0, move;
18550
18551         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18552         // first find outermost bracketing variation
18553         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18554             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18555                 if(*p == '{') wait = '}'; else
18556                 if(*p == '[') wait = ']'; else
18557                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18558                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18559             }
18560             if(*p == wait) wait = NULLCHAR; // closing ]} found
18561             p++;
18562         }
18563         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18564         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18565         end[1] = NULLCHAR; // clip off comment beyond variation
18566         ToNrEvent(currentMove-1);
18567         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18568         // kludge: use ParsePV() to append variation to game
18569         move = currentMove;
18570         ParsePV(start, TRUE, TRUE);
18571         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18572         ClearPremoveHighlights();
18573         CommentPopDown();
18574         ToNrEvent(currentMove+1);
18575 }
18576
18577 void
18578 LoadTheme ()
18579 {
18580     char *p, *q, buf[MSG_SIZ];
18581     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18582         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18583         ParseArgsFromString(buf);
18584         ActivateTheme(TRUE); // also redo colors
18585         return;
18586     }
18587     p = nickName;
18588     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18589     {
18590         int len;
18591         q = appData.themeNames;
18592         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18593       if(appData.useBitmaps) {
18594         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18595                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18596                 appData.liteBackTextureMode,
18597                 appData.darkBackTextureMode );
18598       } else {
18599         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18600                 Col2Text(2),   // lightSquareColor
18601                 Col2Text(3) ); // darkSquareColor
18602       }
18603       if(appData.useBorder) {
18604         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18605                 appData.border);
18606       } else {
18607         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18608       }
18609       if(appData.useFont) {
18610         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18611                 appData.renderPiecesWithFont,
18612                 appData.fontToPieceTable,
18613                 Col2Text(9),    // appData.fontBackColorWhite
18614                 Col2Text(10) ); // appData.fontForeColorBlack
18615       } else {
18616         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18617                 appData.pieceDirectory);
18618         if(!appData.pieceDirectory[0])
18619           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18620                 Col2Text(0),   // whitePieceColor
18621                 Col2Text(1) ); // blackPieceColor
18622       }
18623       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18624                 Col2Text(4),   // highlightSquareColor
18625                 Col2Text(5) ); // premoveHighlightColor
18626         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18627         if(insert != q) insert[-1] = NULLCHAR;
18628         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18629         if(q)   free(q);
18630     }
18631     ActivateTheme(FALSE);
18632 }